openaq-mcp-server

Find air-quality stations and read pollutant observations from government monitors via OpenAQ v3.

Should I use this

Quality & Safety

A
Description quality
100%
Schema completeness
94%
Naming quality
100%
Poisoning risk
100%
Permission match
100%
Protocol compliance
100%

Based on automated analysis of tool definitions and protocol compliance.

Context Cost

~10,398Tokens (tool definitions)
~17.3 KBTypical response size
Significant attention impact (8.12% of 128k context)

This is the approximate number of tokens consumed each time the server's tools are loaded into a model's context. Higher counts reduce the attention available for other tasks.

Install

One-Click Install

Add this to your `claude_desktop_config.json` file:

{
  "mcpServers": {
    "openaq-mcp-server": {
      "command": "node",
      "args": [
        "@cyanheads/openaq-mcp-server"
      ]
    }
  }
}

Runnable packages

npm@cyanheads/openaq-mcp-server0.4.1streamable-http

Remote endpoints

https://openaq.caseyjhand.com/mcpstreamable-http

What it can do

Tool inventory

Tools (7)

๐ŸŸข Read-only๐ŸŸก Write๐Ÿ”ด Deleteโšช Unknown
๐ŸŸขopenaq_find_locations(coordinates, radius, bbox, iso, parametersId, ...)

Find air-quality monitoring stations (measured by physical sensors, not modeled) near a point, within a bounding box, or by country, optionally narrowed to one parameter, one station class (reference monitors or low-cost sensors, mobile or fixed), or one provider network. Returns each station's id, name, coordinates, distance from the query point (when searching by coordinates), country, provider name and id, the parameters its sensors measure, and the timestamp of its most recent data (datetimeLast). Required first step: openaq_get_readings and openaq_get_measurements key on the location id this returns. Coverage is uneven and real โ€” a station only reports the parameters it measures, and the absence of a nearby station means no monitoring there, not clean air. For dense modeled coverage anywhere on Earth, use open-meteo-mcp-server's air-quality tool instead.

Input Schema

{
  "type": "object",
  "properties": {
    "coordinates": {
      "description": "Center point as \"latitude,longitude\" (e.g. \"47.6062,-122.3321\"). Pair with radius for a near-me search. Resolve a place name to coordinates with openstreetmap-mcp-server or open-meteo geocode first. Provide either coordinates+radius OR bbox, not both.",
      "type": "string",
      "pattern": "^-?\\d{1,3}(\\.\\d+)?,-?\\d{1,3}(\\.\\d+)?$"
    },
    "radius": {
      "description": "Search radius in metres around coordinates (1โ€“25000; the API hard-caps at 25000). Default 12000 (~12km). Requires coordinates โ€” a radius sent with only bbox or iso is rejected.",
      "type": "integer",
      "minimum": 1,
      "maximum": 25000
    },
    "bbox": {
      "description": "Bounding box as \"minLon,minLat,maxLon,maxLat\" (west,south,east,north), with minLon โ‰ค maxLon and minLat โ‰ค maxLat. Alternative to coordinates+radius for area sweeps. Results have no distance field (no center point).",
      "type": "string",
      "pattern": "^(-?\\d+(\\.\\d+)?,){3}-?\\d+(\\.\\d+)?$"
    },
    "iso": {
      "description": "Restrict to a country by OpenAQ country code: ISO 3166-1 alpha-2 (e.g. \"US\", \"IN\", \"DE\"; either case), or \"-99\" where OpenAQ lists a country with no ISO code. Take codes from openaq_list_countries. Combine with bbox/coordinates to scope, or use alone for a country-wide list.",
      "type": "string",
      "pattern": "^(?:[A-Za-z]{2}|-99)$"
    },
    "parametersId": {
      "description": "Only return stations that measure this parameter id (e.g. 2 = PM2.5 ยตg/mยณ). Get ids from openaq_list_parameters โ€” the same pollutant has several ids for different units. Narrows the station set; each returned station still lists all its sensors.",
      "type": "integer",
      "exclusiveMinimum": 0,
      "maximum": 9007199254740991
    },
    "monitor": {
      "description": "Station class filter: true returns only reference-grade monitors, false only low-cost sensors. Omit for both.",
      "type": "boolean"
    },
    "mobile": {
      "description": "Mobility filter: true returns only mobile stations, false only fixed ones. Omit for both.",
      "type": "boolean"
    },
    "providersId": {
      "description": "Only return stations from this OpenAQ provider (data network) id โ€” read it from a previous result's providerId (e.g. 119 = AirNow).",
      "type": "integer",
      "exclusiveMinimum": 0,
      "maximum": 9007199254740991
    },
    "limit": {
      "default": 20,
      "description": "Max stations to return (1โ€“100). Default 20. Results are ordered by distance when searching by coordinates.",
      "type": "integer",
      "minimum": 1,
      "maximum": 100
    },
    "page": {
      "default": 1,
      "description": "Which page of results to return (1-based). Default 1. The only way past the 100-station cap: with limit 100, page 2 returns stations 101โ€“200. Distance ordering applies within a page, not across pages, so paging is for iso/bbox sweeps โ€” a near-me coordinates search should stay on page 1. A page past the last one fails with page_exhausted.",
      "type": "integer",
      "minimum": 1,
      "maximum": 9007199254740991
    }
  },
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "additionalProperties": false
}

Output Schema

{
  "type": "object",
  "properties": {
    "locations": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Location id โ€” pass to openaq_get_readings / openaq_get_measurements"
          },
          "name": {
            "type": "string",
            "description": "Station name"
          },
          "locality": {
            "description": "Locality or metro area, when provided",
            "type": [
              "string",
              "null"
            ]
          },
          "country": {
            "anyOf": [
              {
                "type": "object",
                "properties": {
                  "code": {
                    "type": "string",
                    "description": "OpenAQ country code: ISO 3166-1 alpha-2, or \"-99\" where OpenAQ lists none"
                  },
                  "name": {
                    "type": "string",
                    "description": "Country name"
                  }
                },
                "required": [
                  "code",
                  "name"
                ],
                "additionalProperties": false
              },
              {
                "type": "null"
              }
            ],
            "description": "Country the station is in. Null when OpenAQ lists none."
          },
          "coordinates": {
            "anyOf": [
              {
                "type": "object",
                "properties": {
                  "latitude": {
                    "type": "number",
                    "description": "Station latitude (decimal degrees)"
                  },
                  "longitude": {
                    "type": "number",
                    "description": "Station longitude (decimal degrees)"
                  }
                },
                "required": [
                  "latitude",
                  "longitude"
                ],
                "additionalProperties": false
              },
              {
                "type": "null"
              }
            ],
            "description": "Station location. Null when OpenAQ lists no latitude or no longitude."
          },
          "distanceMeters": {
            "description": "Distance from the query coordinates in metres. Null when searching by bbox or iso (no center point).",
            "type": [
              "number",
              "null"
            ]
          },
          "provider": {
            "description": "Data provider / network (e.g. \"AirNow\", \"OpenAQ LCS\"). Null when OpenAQ lists none.",
            "type": [
              "string",
              "null"
            ]
          },
          "providerId": {
            "description": "OpenAQ provider id โ€” pass as providersId to restrict a search to this network. Null when OpenAQ lists no provider.",
            "type": [
              "number",
              "null"
            ]
          },
          "isMonitor": {
            "type": "boolean",
            "description": "True for reference-grade government monitors; false for low-cost sensors. Reference monitors are more reliable for regulatory comparison."
          },
          "isMobile": {
            "type": "boolean",
            "description": "True if the station is mobile (coordinates may vary over time)"
          },
          "parameters": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "number",
                  "description": "Parameter id โ€” use as parametersId in get_readings / get_measurements"
                },
                "name": {
                  "type": "string",
                  "description": "Pollutant code (e.g. \"pm25\", \"o3\")"
                },
                "unit": {
                  "type": "string",
                  "description": "Measurement unit for this sensor (e.g. \"ยตg/mยณ\", \"ppm\"). Units vary by sensor โ€” never assume."
                },
                "displayName": {
                  "description": "Human-readable pollutant name",
                  "type": [
                    "string",
                    "null"
                  ]
                }
              },
              "required": [
                "id",
                "name",
                "unit",
                "displayName"
              ],
              "additionalProperties": false,
              "description": "A parameter the station measures, with its sensor unit"
            },
            "description": "Parameters this station measures, each with its sensor unit. The station has one sensor per parameter."
          },
          "datetimeLast": {
            "anyOf": [
              {
                "type": "object",
                "properties": {
                  "utc": {
                    "type": "string",
                    "description": "Timestamp in UTC (ISO 8601)"
                  },
                  "local": {
                    "type": "string",
                    "description": "Timestamp in the station's local timezone"
                  }
                },
                "required": [
                  "utc",
                  "local"
                ],
                "additionalProperties": false
              },
              {
                "type": "null"
              }
            ],
            "description": "Timestamp of the station's most recent measurement. Tells you whether \"latest\" will be minutes or hours/days old. Null if the station has never reported."
          },
          "datetimeFirst": {
            "anyOf": [
              {
                "type": "object",
                "properties": {
                  "utc": {
                    "type": "string",
                    "description": "Timestamp in UTC (ISO 8601)"
                  },
                  "local": {
                    "type": "string",
                    "description": "Timestamp in the station's local timezone"
                  }
                },
                "required": [
                  "utc",
                  "local"
                ],
                "additionalProperties": false
              },
              {
                "type": "null"
              }
            ],
            "description": "Timestamp of the station's first available measurement."
          }
        },
        "required": [
          "id",
          "name",
          "locality",
          "country",
          "coordinates",
          "distanceMeters",
          "provider",
          "providerId",
          "isMonitor",
          "isMobile",
          "parameters",
          "datetimeLast",
          "datetimeFirst"
        ],
        "additionalProperties": false,
        "description": "A matching monitoring station with its sensors and data span"
      },
      "description": "Matching stations on this page, never empty: a query with no match fails with no_locations_found (no monitoring coverage, NOT clean air), and a page past the last with page_exhausted."
    },
    "totalCount": {
      "type": "number",
      "description": "Stations counted through this page: (page โˆ’ 1) ร— limit plus the stations returned. Exact on a page that came back short of the limit (the last page); a floor when totalCountIsLowerBound is true."
    },
    "totalCountIsLowerBound": {
      "description": "True when this page came back full: at least totalCount stations match, and the next page may hold more.",
      "type": "boolean"
    },
    "truncated": {
      "description": "True when this page came back full (the limit was reached), so the next page may hold more stations.",
      "type": "boolean"
    },
    "shown": {
      "description": "Number of stations returned.",
      "type": "number"
    },
    "cap": {
      "description": "The limit that was applied.",
      "type": "number"
    },
    "notice": {
      "description": "Guidance on a full page: the next page to request, or how to narrow the search.",
      "type": "string"
    },
    "error": {
      "description": "Present when the call failed. Absent on success.",
      "type": "object",
      "properties": {
        "code": {
          "type": "integer",
          "minimum": -9007199254740991,
          "maximum": 9007199254740991,
          "description": "JSON-RPC error code for this failure."
        },
        "message": {
          "type": "string",
          "description": "Human-readable description of what went wrong."
        },
        "data": {
          "type": "object",
          "properties": {
            "reason": {
              "type": "string",
              "description": "Machine-readable failure mode. Declared by this tool: `no_locations_found`: No monitoring stations match the given area or filters. `page_exhausted`: A page past the first returned no stations โ€” the results end before it. `no_search_scope`: None of coordinates, bbox, or iso was provided. `invalid_search_scope`: coordinates and bbox were both provided, or radius was provided without coordinates. `upstream_error`: OpenAQ returned 5xx or an unreadable body on every retry. `rate_limited`: OpenAQ returned 429 โ€” the request budget for this key is exhausted. `upstream_timeout`: OpenAQ did not respond within the request timeout on every retry. `invalid_api_key`: OpenAQ returned 401 โ€” the configured OPENAQ_API_KEY is missing, invalid, or revoked. Other values are possible when a failure originates below the handler.",
              "examples": [
                "no_locations_found",
                "page_exhausted",
                "no_search_scope",
                "invalid_search_scope",
                "upstream_error",
                "rate_limited",
                "upstream_timeout",
                "invalid_api_key"
              ]
            },
            "recovery": {
              "description": "Actionable next step for the caller.",
              "type": "object",
              "properties": {
                "hint": {
                  "type": "string"
                }
              },
              "required": [
                "hint"
              ],
              "additionalProperties": {}
            },
            "retryable": {
              "description": "Whether retrying may succeed.",
              "type": "boolean"
            }
          },
          "additionalProperties": {}
        }
      },
      "required": [
        "code",
        "message"
      ],
      "additionalProperties": {}
    }
  },
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "additionalProperties": false,
  "anyOf": [
    {
      "not": {
        "required": [
          "error"
        ]
      },
      "required": [
        "locations",
        "totalCount"
      ]
    },
    {
      "required": [
        "error"
      ]
    }
  ]
}
๐ŸŸขopenaq_get_readings(locationId, coordinates, parametersId)

Latest measured value for every sensor at a monitoring station โ€” the current-conditions tool. Returns one record per parameter, each with the value, its unit, the UTC and local timestamp, and the sensor id, joined so every value carries its pollutant and unit (the raw latest feed is keyed only by sensor id). The station block names its provider (for attribution) and timezone. Pass a locationId from openaq_find_locations, or pass coordinates to auto-resolve to the nearest station that measures the requested parametersId. Data recency varies by station reporting cadence โ€” read each value's timestamp to know whether "latest" is minutes or hours old. These are measured observations with coverage gaps, not a modeled grid.

Input Schema

{
  "type": "object",
  "properties": {
    "locationId": {
      "description": "Station id from openaq_find_locations. Provide this OR coordinates. When set, returns the latest value for every sensor at this station.",
      "type": "integer",
      "exclusiveMinimum": 0,
      "maximum": 9007199254740991
    },
    "coordinates": {
      "description": "Fallback \"latitude,longitude\" when you do not have a locationId โ€” resolves to the nearest station (within 25km) that measures parametersId, then reads its latest values. Requires parametersId.",
      "type": "string",
      "pattern": "^-?\\d{1,3}(\\.\\d+)?,-?\\d{1,3}(\\.\\d+)?$"
    },
    "parametersId": {
      "description": "Required with coordinates: which parameter id the nearest station must measure (get ids from openaq_list_parameters). With locationId, optionally filters the returned values to this parameter id; omit to get all sensors.",
      "type": "integer",
      "exclusiveMinimum": 0,
      "maximum": 9007199254740991
    }
  },
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "additionalProperties": false
}

Output Schema

{
  "type": "object",
  "properties": {
    "location": {
      "type": "object",
      "properties": {
        "id": {
          "type": "number",
          "description": "Station id"
        },
        "name": {
          "type": "string",
          "description": "Station name"
        },
        "coordinates": {
          "anyOf": [
            {
              "type": "object",
              "properties": {
                "latitude": {
                  "type": "number",
                  "description": "Station latitude (decimal degrees)"
                },
                "longitude": {
                  "type": "number",
                  "description": "Station longitude (decimal degrees)"
                }
              },
              "required": [
                "latitude",
                "longitude"
              ],
              "additionalProperties": false
            },
            {
              "type": "null"
            }
          ],
          "description": "Station coordinates. Null when OpenAQ lists no latitude or no longitude."
        },
        "provider": {
          "description": "Network that operates the station (e.g. \"AirNow\") โ€” cite it alongside OpenAQ. Null when OpenAQ lists none.",
          "type": [
            "string",
            "null"
          ]
        },
        "providerId": {
          "description": "Provider id, usable as providersId in openaq_find_locations. Null when OpenAQ lists none.",
          "type": [
            "number",
            "null"
          ]
        },
        "timezone": {
          "description": "IANA timezone of the station",
          "type": [
            "string",
            "null"
          ]
        },
        "distanceMeters": {
          "description": "Distance from query coordinates in metres, when resolved via coordinates; null when called by locationId",
          "type": [
            "number",
            "null"
          ]
        },
        "datetimeLast": {
          "anyOf": [
            {
              "type": "object",
              "properties": {
                "utc": {
                  "type": "string",
                  "description": "Timestamp in UTC (ISO 8601)"
                },
                "local": {
                  "type": "string",
                  "description": "Timestamp in the station's local timezone"
                }
              },
              "required": [
                "utc",
                "local"
              ],
              "additionalProperties": false
            },
            {
              "type": "null"
            }
          ],
          "description": "Timestamp of the station's most recent measurement โ€” tells you whether \"latest\" is minutes or hours old before reading per-value timestamps. Null if the station has never reported."
        }
      },
      "required": [
        "id",
        "name",
        "coordinates",
        "provider",
        "providerId",
        "timezone",
        "distanceMeters",
        "datetimeLast"
      ],
      "additionalProperties": false,
      "description": "The station these readings came from"
    },
    "readings": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "parameter": {
            "type": "object",
            "properties": {
              "id": {
                "type": "number",
                "description": "Parameter id"
              },
              "name": {
                "type": "string",
                "description": "Pollutant code (e.g. \"pm25\")"
              },
              "displayName": {
                "description": "Human-readable pollutant name",
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "id",
              "name",
              "displayName"
            ],
            "additionalProperties": false,
            "description": "What was measured"
          },
          "value": {
            "type": "number",
            "description": "Measured concentration"
          },
          "unit": {
            "type": "string",
            "description": "Unit for this value (e.g. \"ยตg/mยณ\", \"ppm\", \"ppb\"). Always read it โ€” units differ across stations and pollutants; the value is meaningless without it."
          },
          "sensorId": {
            "type": "number",
            "description": "Sensor id โ€” use the corresponding locationId + parametersId to fetch this sensor's history via openaq_get_measurements"
          },
          "datetimeUtc": {
            "type": "string",
            "description": "Measurement time, UTC (ISO 8601)"
          },
          "datetimeLocal": {
            "type": "string",
            "description": "Measurement time in the station's local timezone"
          }
        },
        "required": [
          "parameter",
          "value",
          "unit",
          "sensorId",
          "datetimeUtc",
          "datetimeLocal"
        ],
        "additionalProperties": false,
        "description": "Latest value for one sensor, with its pollutant and unit"
      },
      "description": "Latest value per sensor. An old datetime means the station reports infrequently or is stale โ€” not that the value is current."
    },
    "notice": {
      "description": "Set when coordinate resolution compared a full 1,000-station page: more stations may match, so the station returned is the nearest of the first 1,000 OpenAQ lists, not necessarily the nearest overall.",
      "type": "string"
    },
    "error": {
      "description": "Present when the call failed. Absent on success.",
      "type": "object",
      "properties": {
        "code": {
          "type": "integer",
          "minimum": -9007199254740991,
          "maximum": 9007199254740991,
          "description": "JSON-RPC error code for this failure."
        },
        "message": {
          "type": "string",
          "description": "Human-readable description of what went wrong."
        },
        "data": {
          "type": "object",
          "properties": {
            "reason": {
              "type": "string",
              "description": "Machine-readable failure mode. Declared by this tool: `location_not_found`: The locationId does not exist (API returns {\"detail\":\"Location not found\"}). `parameter_not_at_location`: No sensor at the resolved station measures parametersId (often the wrong unit variant was chosen). `no_station_near_coordinates`: The 25km auto-resolution sweep found no station measuring the requested parametersId. `no_recent_values`: The station has the requested sensors but its latest feed carried no values for them. `invalid_location_scope`: Both locationId and coordinates were provided, or neither was. `missing_coordinates_parameter`: coordinates was provided without parametersId. `upstream_error`: OpenAQ returned 5xx or an unreadable body on every retry. `rate_limited`: OpenAQ returned 429 โ€” the request budget for this key is exhausted. `upstream_timeout`: OpenAQ did not respond within the request timeout on every retry. `invalid_api_key`: OpenAQ returned 401 โ€” the configured OPENAQ_API_KEY is missing, invalid, or revoked. Other values are possible when a failure originates below the handler.",
              "examples": [
                "location_not_found",
                "parameter_not_at_location",
                "no_station_near_coordinates",
                "no_recent_values",
                "invalid_location_scope",
                "missing_coordinates_parameter",
                "upstream_error",
                "rate_limited",
                "upstream_timeout",
                "invalid_api_key"
              ]
            },
            "recovery": {
              "description": "Actionable next step for the caller.",
              "type": "object",
              "properties": {
                "hint": {
                  "type": "string"
                }
              },
              "required": [
                "hint"
              ],
              "additionalProperties": {}
            },
            "retryable": {
              "description": "Whether retrying may succeed.",
              "type": "boolean"
            }
          },
          "additionalProperties": {}
        }
      },
      "required": [
        "code",
        "message"
      ],
      "additionalProperties": {}
    }
  },
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "additionalProperties": false,
  "anyOf": [
    {
      "not": {
        "required": [
          "error"
        ]
      },
      "required": [
        "location",
        "readings"
      ]
    },
    {
      "required": [
        "error"
      ]
    }
  ]
}
๐ŸŸขopenaq_get_measurements(locationId, parametersId, datetimeFrom, datetimeTo, aggregation, ...)

Historical measurement series for one pollutant at one station over a date range โ€” for trend analysis and "was last week worse than the monthly average?". Pass a locationId and a parametersId and work in stations โ€” you get the series for that pollutant at that station. Choose aggregation: raw (every reported value), hourly, or daily โ€” daily and hourly add a per-bucket statistical summary (min, median, max, mean, sd). A date-only bound means the station's local calendar day. Large ranges produce thousands of rows and stage on a DataCanvas: the response returns a preview plus a canvasId and table name โ€” call openaq_dataframe_describe on the canvasId for the table's columns, then openaq_dataframe_query to run SQL over it. Passing a canvas_id stages the series there whatever its size, so two stations land on one canvas for a side-by-side comparison. Values carry their unit; the server never converts between ยตg/mยณ, ppm, and ppb.

Input Schema

{
  "type": "object",
  "properties": {
    "locationId": {
      "type": "integer",
      "exclusiveMinimum": 0,
      "maximum": 9007199254740991,
      "description": "Station id from openaq_find_locations."
    },
    "parametersId": {
      "type": "integer",
      "exclusiveMinimum": 0,
      "maximum": 9007199254740991,
      "description": "Parameter id to pull the series for (e.g. 2 = PM2.5 ยตg/mยณ). Get ids from openaq_list_parameters. Must be a parameter the station measures โ€” find_locations lists each station's parameters."
    },
    "datetimeFrom": {
      "description": "Start of the range, inclusive. A date \"YYYY-MM-DD\" opens at local midnight of that day in the station's timezone (UTC midnight when OpenAQ lists none); a full UTC \"YYYY-MM-DDTHH:MM:SSZ\" is sent as is. Omit to start from the sensor's earliest data โ€” the series runs oldest first, so on a long-running station an open start fills the row cap with its oldest values; set datetimeFrom to reach recent ones. effectiveRange echoes the instant sent.",
      "type": "string",
      "pattern": "^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}Z)?$"
    },
    "datetimeTo": {
      "description": "End of the range, inclusive. A date \"YYYY-MM-DD\" covers that whole station-local day, closing at the next local midnight, so a DST day spans 23 or 25 hours; a full UTC \"YYYY-MM-DDTHH:MM:SSZ\" is sent as is. Must land after datetimeFrom โ€” the two forms mix freely, so \"2026-06-25\" to \"2026-06-25\" is a valid one-day range. Omit for \"up to now\". effectiveRange echoes the instant sent.",
      "type": "string",
      "pattern": "^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}Z)?$"
    },
    "aggregation": {
      "default": "raw",
      "description": "Time bucketing. \"raw\" = every reported value (often hourly at source). \"hourly\"/\"daily\" = server-side rollups with a statistical summary per bucket; an hour is labeled by the time it ends, and a day is the station's local calendar day. Use \"daily\" for multi-month trends to keep the series small; \"raw\" for fine-grained recent analysis.",
      "type": "string",
      "enum": [
        "raw",
        "hourly",
        "daily"
      ]
    },
    "limit": {
      "default": 1000,
      "description": "Max rows per page from the API (1โ€“1000). Default 1000. The tool pages internally up to the 5000-row pull ceiling.",
      "type": "integer",
      "minimum": 1,
      "maximum": 1000
    },
    "canvas_id": {
      "description": "DataCanvas id from a prior openaq_get_measurements call, to put this series on the same canvas (e.g. to compare two stations' series side by side). Supplying it stages the series whatever its size. Reuse stages one table per sensor, so a second sensor adds a table while the same sensor overwrites its earlier series โ€” the response says so when that happens. Omit to start fresh; the response returns a new canvas_id when the series overflows the inline preview.",
      "type": "string",
      "pattern": "^[A-Za-z0-9_-]{10}$"
    }
  },
  "required": [
    "locationId",
    "parametersId"
  ],
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "additionalProperties": false
}

Output Schema

{
  "type": "object",
  "properties": {
    "location": {
      "type": "object",
      "properties": {
        "id": {
          "type": "number",
          "description": "Station id"
        },
        "name": {
          "type": "string",
          "description": "Station name"
        },
        "provider": {
          "description": "Network that operates the station โ€” cite it alongside OpenAQ. Null when OpenAQ lists none.",
          "type": [
            "string",
            "null"
          ]
        },
        "providerId": {
          "description": "Provider id, usable as providersId in openaq_find_locations. Null when OpenAQ lists none.",
          "type": [
            "number",
            "null"
          ]
        },
        "timezone": {
          "description": "IANA timezone of the station (e.g. \"America/Los_Angeles\"). Daily buckets and date-only bounds follow its calendar days. Null when OpenAQ lists none.",
          "type": [
            "string",
            "null"
          ]
        }
      },
      "required": [
        "id",
        "name",
        "provider",
        "providerId",
        "timezone"
      ],
      "additionalProperties": false,
      "description": "Station the series came from"
    },
    "parameter": {
      "type": "object",
      "properties": {
        "id": {
          "type": "number",
          "description": "Parameter id"
        },
        "name": {
          "type": "string",
          "description": "Pollutant code"
        },
        "unit": {
          "type": "string",
          "description": "Unit for every value in this series. The server does not convert units."
        },
        "displayName": {
          "description": "Human-readable pollutant name",
          "type": [
            "string",
            "null"
          ]
        }
      },
      "required": [
        "id",
        "name",
        "unit",
        "displayName"
      ],
      "additionalProperties": false,
      "description": "What was measured, resolved from the station's sensor"
    },
    "sensorId": {
      "type": "number",
      "description": "Resolved sensor id the series was pulled from"
    },
    "aggregation": {
      "type": "string",
      "enum": [
        "raw",
        "hourly",
        "daily"
      ],
      "description": "Bucketing applied"
    },
    "series": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "datetimeFrom": {
            "type": "string",
            "description": "Bucket start, UTC (ISO 8601)"
          },
          "datetimeTo": {
            "type": "string",
            "description": "Bucket end, UTC (ISO 8601)"
          },
          "value": {
            "description": "Value for the bucket (the measurement for raw; the bucket aggregate for hourly/daily). Null for a gap bucket the sensor reported nothing into โ€” the bucket is kept so the series stays evenly spaced on the time axis",
            "type": [
              "number",
              "null"
            ]
          },
          "summary": {
            "anyOf": [
              {
                "type": "object",
                "properties": {
                  "min": {
                    "description": "Minimum reading in the bucket",
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "median": {
                    "description": "Median reading in the bucket",
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "max": {
                    "description": "Maximum reading in the bucket",
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "avg": {
                    "description": "Mean reading in the bucket",
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "sd": {
                    "description": "Standard deviation โ€” null when only one reading in the bucket",
                    "type": [
                      "number",
                      "null"
                    ]
                  }
                },
                "required": [
                  "min",
                  "median",
                  "max",
                  "avg",
                  "sd"
                ],
                "additionalProperties": false
              },
              {
                "type": "null"
              }
            ],
            "description": "Per-bucket statistics โ€” present for hourly/daily, null for raw. Every field is null in a gap bucket"
          },
          "percentComplete": {
            "description": "Coverage of the bucket as OpenAQ reports it โ€” observed readings as a percentage of expected ones. Low values flag gappy data. Usually 0โ€“100, but it exceeds 100 when a bucket holds more readings than expected, e.g. 200 on the hour a DST fall-back repeats",
            "type": [
              "number",
              "null"
            ]
          },
          "flagged": {
            "type": "boolean",
            "description": "True if the source flagged this value (quality concern)"
          }
        },
        "required": [
          "datetimeFrom",
          "datetimeTo",
          "value",
          "summary",
          "percentComplete",
          "flagged"
        ],
        "additionalProperties": false,
        "description": "One bucket in the series, with its value and (for rollups) statistics"
      },
      "description": "The (possibly previewed) series in the order OpenAQ returns it (oldest first). An hourly/daily series either skips a missing bucket or returns it with a null value โ€” gapCount and gaps report both. Every row here is also rendered in the text output. When truncated, this is a preview of pulledCount rows โ€” query canvasId for the rest."
    },
    "rowCount": {
      "type": "number",
      "description": "Rows in this response (preview length when spilled)"
    },
    "pulledCount": {
      "type": "number",
      "description": "Rows pulled from OpenAQ, at most 5000 โ€” the canvas table's row count when canvasId is present. Equals rowCount when the whole series fit inline; larger when series is a preview."
    },
    "pullComplete": {
      "type": "boolean",
      "description": "True when pulledCount is the whole series for the requested range. False when the 5000-row cap or a failed page stopped the pull early โ€” the rows past that point are in neither this response nor the canvas table, and the notice says how to reach them."
    },
    "canvasId": {
      "description": "DataCanvas id holding the staged series โ€” pulledCount rows of it. Call openaq_dataframe_describe on this id for the table's columns, then openaq_dataframe_query to run SQL. Present whenever staging succeeded, which includes a series that fit inline on a canvas_id you supplied.",
      "type": "string"
    },
    "tableName": {
      "description": "Canvas table holding the staged series (e.g. \"measurements_1701\"). openaq_dataframe_describe lists its columns; reference this name in openaq_dataframe_query SQL. One table per sensor, so re-staging the same sensor on this canvas overwrites it.",
      "type": "string"
    },
    "truncated": {
      "description": "True when the series exceeded the inline limit, so series is a preview of the pulled rows. Absent/false when every pulled row is inline. It describes the preview only โ€” canvasId reports whether the rows were staged, and pullComplete whether the pull itself finished.",
      "type": "boolean"
    },
    "totalCount": {
      "type": "number",
      "description": "Rows in the full series for this range. A floor rather than an exact count when totalCountIsLowerBound is set; never below pulledCount."
    },
    "totalCountIsLowerBound": {
      "description": "Set when totalCount is only a floor: the pull stopped early and OpenAQ reported the range total as \">N\" instead of an exact number, so more rows exist than totalCount states. Absent when the count is exact.",
      "type": "boolean"
    },
    "effectiveRange": {
      "type": "object",
      "properties": {
        "datetimeFrom": {
          "description": "Lower bound sent to OpenAQ, UTC. Null when datetimeFrom was omitted.",
          "type": [
            "string",
            "null"
          ]
        },
        "datetimeTo": {
          "description": "Upper bound sent to OpenAQ, UTC. Null when datetimeTo was omitted.",
          "type": [
            "string",
            "null"
          ]
        }
      },
      "required": [
        "datetimeFrom",
        "datetimeTo"
      ],
      "additionalProperties": false,
      "description": "The range sent to OpenAQ as UTC instants โ€” date-only bounds expanded to the station's local day."
    },
    "gapCount": {
      "description": "Missing intervals inside an hourly or daily series โ€” a span between buckets that do not touch, or a bucket with a null value, merged where contiguous โ€” counted over every pulled row, not only the preview. 0 when nothing is missing; absent for raw, whose rows follow no fixed cadence.",
      "type": "number"
    },
    "gaps": {
      "description": "The first 20 missing intervals, oldest first. Omitted when gapCount is 0.",
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "datetimeFrom": {
            "type": "string",
            "description": "Start of the missing interval, UTC"
          },
          "datetimeTo": {
            "type": "string",
            "description": "End of the missing interval, UTC"
          }
        },
        "required": [
          "datetimeFrom",
          "datetimeTo"
        ],
        "additionalProperties": false,
        "description": "One missing interval"
      }
    },
    "notice": {
      "description": "What limited this response or where the rest of it lives โ€” the row cap, a failed page, a station with no timezone, an edge bucket clipped by the range, missing intervals, DataCanvas being unavailable, or the canvas table the series was staged on and the tools that read it.",
      "type": "string"
    },
    "error": {
      "description": "Present when the call failed. Absent on success.",
      "type": "object",
      "properties": {
        "code": {
          "type": "integer",
          "minimum": -9007199254740991,
          "maximum": 9007199254740991,
          "description": "JSON-RPC error code for this failure."
        },
        "message": {
          "type": "string",
          "description": "Human-readable description of what went wrong."
        },
        "data": {
          "type": "object",
          "properties": {
            "reason": {
              "type": "string",
              "description": "Machine-readable failure mode. Declared by this tool: `location_not_found`: The locationId does not exist. `parameter_not_at_location`: No sensor at the station measures parametersId (often the wrong unit variant was chosen). `no_data_for_range`: The sensor has no measurements in the requested date range. `invalid_date_range`: The range is empty โ€” once date-only bounds are expanded to the station's local day, datetimeTo does not land after datetimeFrom. `canvas_not_found`: The supplied canvas_id is unknown or has expired, so the series cannot be staged onto it. `upstream_error`: OpenAQ returned 5xx or an unreadable body on every retry. `rate_limited`: OpenAQ returned 429 โ€” the request budget for this key is exhausted. `upstream_timeout`: OpenAQ did not respond within the request timeout on every retry. `invalid_api_key`: OpenAQ returned 401 โ€” the configured OPENAQ_API_KEY is missing, invalid, or revoked. Other values are possible when a failure originates below the handler.",
              "examples": [
                "location_not_found",
                "parameter_not_at_location",
                "no_data_for_range",
                "invalid_date_range",
                "canvas_not_found",
                "upstream_error",
                "rate_limited",
                "upstream_timeout",
                "invalid_api_key"
              ]
            },
            "recovery": {
              "description": "Actionable next step for the caller.",
              "type": "object",
              "properties": {
                "hint": {
                  "type": "string"
                }
              },
              "required": [
                "hint"
              ],
              "additionalProperties": {}
            },
            "retryable": {
              "description": "Whether retrying may succeed.",
              "type": "boolean"
            }
          },
          "additionalProperties": {}
        }
      },
      "required": [
        "code",
        "message"
      ],
      "additionalProperties": {}
    }
  },
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "additionalProperties": false,
  "anyOf": [
    {
      "not": {
        "required": [
          "error"
        ]
      },
      "required": [
        "location",
        "parameter",
        "sensorId",
        "aggregation",
        "series",
        "rowCount",
        "pulledCount",
        "pullComplete",
        "totalCount",
        "effectiveRange"
      ]
    },
    {
      "required": [
        "error"
      ]
    }
  ]
}
๐ŸŸขopenaq_list_parameters(query, pollutantsOnly)

Catalog of every measurable pollutant and its canonical unit: id, code, display name, unit, and a one-line description (pm25, pm10, o3, no2, so2, co, bc, and more). This is the unit-disambiguation reference โ€” the same pollutant exists under several ids with different units (CO is id 4 in ยตg/mยณ, id 8 in ppm, id 102 in ppb), so use this to pick the exact parametersId for openaq_find_locations / openaq_get_readings / openaq_get_measurements and to interpret a reading's unit. A small bounded catalog fetched live from OpenAQ.

Input Schema

{
  "type": "object",
  "properties": {
    "query": {
      "description": "Case-insensitive filter over the bounded parameter catalog by code, display name, and description (e.g. \"pm\" for particulates, \"ozone\", \"co\"). Omit to list everything.",
      "type": "string"
    },
    "pollutantsOnly": {
      "default": false,
      "description": "When true, exclude meteorological/auxiliary parameters (temperature, humidity, wind, pressure, particle-count channels) and return only air pollutants. Default false (full catalog).",
      "type": "boolean"
    }
  },
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "additionalProperties": false
}

Output Schema

{
  "type": "object",
  "properties": {
    "parameters": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Parameter id โ€” the precise selector for the other tools (unit-specific)"
          },
          "name": {
            "type": "string",
            "description": "Pollutant code (e.g. \"pm25\", \"o3\", \"co\")"
          },
          "displayName": {
            "description": "Human-readable name (e.g. \"PM2.5\", \"Oโ‚ƒ mass\")",
            "type": [
              "string",
              "null"
            ]
          },
          "unit": {
            "type": "string",
            "description": "Canonical measurement unit for this id (e.g. \"ยตg/mยณ\", \"ppm\", \"ppb\"). The same pollutant code appears under multiple ids with different units."
          },
          "description": {
            "description": "One-line description of the pollutant",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "name",
          "displayName",
          "unit",
          "description"
        ],
        "additionalProperties": false,
        "description": "A measurable parameter with its canonical unit"
      },
      "description": "Matching parameters. Multiple rows can share a name with different ids/units โ€” pick the id whose unit you want."
    },
    "totalCount": {
      "type": "number",
      "description": "Total parameters matched after filtering."
    },
    "notice": {
      "description": "Guidance when the query matched nothing.",
      "type": "string"
    },
    "error": {
      "description": "Present when the call failed. Absent on success.",
      "type": "object",
      "properties": {
        "code": {
          "type": "integer",
          "minimum": -9007199254740991,
          "maximum": 9007199254740991,
          "description": "JSON-RPC error code for this failure."
        },
        "message": {
          "type": "string",
          "description": "Human-readable description of what went wrong."
        },
        "data": {
          "type": "object",
          "properties": {
            "reason": {
              "type": "string",
              "description": "Machine-readable failure mode. Declared by this tool: `upstream_error`: OpenAQ /parameters returned 5xx or an unreadable body on every retry. `rate_limited`: OpenAQ returned 429 โ€” the request budget for this key is exhausted. `upstream_timeout`: OpenAQ /parameters did not respond within the request timeout on every retry. `invalid_api_key`: OpenAQ returned 401 โ€” the configured OPENAQ_API_KEY is missing, invalid, or revoked. Other values are possible when a failure originates below the handler.",
              "examples": [
                "upstream_error",
                "rate_limited",
                "upstream_timeout",
                "invalid_api_key"
              ]
            },
            "recovery": {
              "description": "Actionable next step for the caller.",
              "type": "object",
              "properties": {
                "hint": {
                  "type": "string"
                }
              },
              "required": [
                "hint"
              ],
              "additionalProperties": {}
            },
            "retryable": {
              "description": "Whether retrying may succeed.",
              "type": "boolean"
            }
          },
          "additionalProperties": {}
        }
      },
      "required": [
        "code",
        "message"
      ],
      "additionalProperties": {}
    }
  },
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "additionalProperties": false,
  "anyOf": [
    {
      "not": {
        "required": [
          "error"
        ]
      },
      "required": [
        "parameters",
        "totalCount"
      ]
    },
    {
      "required": [
        "error"
      ]
    }
  ]
}
๐ŸŸขopenaq_list_countries(query, parametersId, limit, page)

Catalog of country-level coverage: id, OpenAQ country code, name, the date span of available station data (datetimeFirst/datetimeLast), and which parameters are measured anywhere in that country. The availability check before a regional sweep โ€” answers "which countries have NO2 monitoring?" and tells you whether a country has recent data before you call openaq_find_locations. Coverage is uneven worldwide; this surfaces where measured data exists. Results come a page at a time (20 countries by default); totalCount is the full filtered count.

Input Schema

{
  "type": "object",
  "properties": {
    "query": {
      "description": "Case-insensitive filter over the country catalog by code and name. A two-letter query matches an exact ISO 3166-1 alpha-2 code first (e.g. \"US\" โ†’ United States) and falls back to substrings when no code matches; longer queries match as substrings (e.g. \"united\", \"germany\"). Omit to page through the whole catalog.",
      "type": "string"
    },
    "parametersId": {
      "description": "Only return countries that measure this parameter id somewhere (e.g. 2 = PM2.5 ยตg/mยณ) โ€” the one-call answer to \"which countries have NO2 monitoring?\". Get ids from openaq_list_parameters; the same pollutant has several ids for different units. Composes with query.",
      "type": "integer",
      "exclusiveMinimum": 0,
      "maximum": 9007199254740991
    },
    "limit": {
      "default": 20,
      "description": "Max countries to return (1โ€“100). Default 20. Applied after query and parametersId, in OpenAQ catalog order.",
      "type": "integer",
      "minimum": 1,
      "maximum": 100
    },
    "page": {
      "default": 1,
      "description": "Which page of the filtered list to return (1-based). Default 1. With limit 20, page 2 returns countries 21โ€“40. A page past the last one returns no countries and a notice naming the last page.",
      "type": "integer",
      "minimum": 1,
      "maximum": 9007199254740991
    }
  },
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "additionalProperties": false
}

Output Schema

{
  "type": "object",
  "properties": {
    "countries": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Country id (OpenAQ internal)"
          },
          "code": {
            "type": "string",
            "description": "OpenAQ country code: ISO 3166-1 alpha-2, or \"-99\" where OpenAQ has none โ€” pass as iso to openaq_find_locations"
          },
          "name": {
            "type": "string",
            "description": "Country name"
          },
          "datetimeFirst": {
            "description": "UTC timestamp of the earliest available measurement in this country (ISO 8601)",
            "type": [
              "string",
              "null"
            ]
          },
          "datetimeLast": {
            "description": "UTC timestamp of the most recent measurement โ€” recent means the country has live coverage",
            "type": [
              "string",
              "null"
            ]
          },
          "parameters": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "number",
                  "description": "Parameter id measured somewhere in this country"
                },
                "name": {
                  "type": "string",
                  "description": "Pollutant code"
                },
                "unit": {
                  "type": "string",
                  "description": "Unit for this parameter id"
                }
              },
              "required": [
                "id",
                "name",
                "unit"
              ],
              "additionalProperties": false,
              "description": "A parameter measured somewhere in this country"
            },
            "description": "Parameters measured anywhere in this country โ€” a coverage hint, not a per-station guarantee"
          }
        },
        "required": [
          "id",
          "code",
          "name",
          "datetimeFirst",
          "datetimeLast",
          "parameters"
        ],
        "additionalProperties": false,
        "description": "A country with its coverage span and measured parameters"
      },
      "description": "Matching countries with coverage metadata."
    },
    "totalCount": {
      "type": "number",
      "description": "Countries matched after query and parametersId, across every page."
    },
    "truncated": {
      "description": "True when more matching countries follow on later pages.",
      "type": "boolean"
    },
    "shown": {
      "description": "Number of countries returned on this page.",
      "type": "number"
    },
    "cap": {
      "description": "The limit that was applied.",
      "type": "number"
    },
    "notice": {
      "description": "Guidance when the filters matched nothing, when more pages follow (the next page to request), or when the page is past the last one.",
      "type": "string"
    },
    "error": {
      "description": "Present when the call failed. Absent on success.",
      "type": "object",
      "properties": {
        "code": {
          "type": "integer",
          "minimum": -9007199254740991,
          "maximum": 9007199254740991,
          "description": "JSON-RPC error code for this failure."
        },
        "message": {
          "type": "string",
          "description": "Human-readable description of what went wrong."
        },
        "data": {
          "type": "object",
          "properties": {
            "reason": {
              "type": "string",
              "description": "Machine-readable failure mode. Declared by this tool: `upstream_error`: OpenAQ /countries returned 5xx or an unreadable body on every retry. `rate_limited`: OpenAQ returned 429 โ€” the request budget for this key is exhausted. `upstream_timeout`: OpenAQ /countries did not respond within the request timeout on every retry. `invalid_api_key`: OpenAQ returned 401 โ€” the configured OPENAQ_API_KEY is missing, invalid, or revoked. Other values are possible when a failure originates below the handler.",
              "examples": [
                "upstream_error",
                "rate_limited",
                "upstream_timeout",
                "invalid_api_key"
              ]
            },
            "recovery": {
              "description": "Actionable next step for the caller.",
              "type": "object",
              "properties": {
                "hint": {
                  "type": "string"
                }
              },
              "required": [
                "hint"
              ],
              "additionalProperties": {}
            },
            "retryable": {
              "description": "Whether retrying may succeed.",
              "type": "boolean"
            }
          },
          "additionalProperties": {}
        }
      },
      "required": [
        "code",
        "message"
      ],
      "additionalProperties": {}
    }
  },
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "additionalProperties": false,
  "anyOf": [
    {
      "not": {
        "required": [
          "error"
        ]
      },
      "required": [
        "countries",
        "totalCount"
      ]
    },
    {
      "required": [
        "error"
      ]
    }
  ]
}
๐ŸŸขopenaq_dataframe_query(canvas_id, sql)

Run a read-only SQL SELECT against the measurement tables openaq_get_measurements staged on a DataCanvas. Reference tables by the name the measurements call returned (measurements_<sensorId>). For aggregation (monthly means, exceedance counts) and cross-sensor comparison over series too large to inline. Only SELECT is allowed โ€” writes, DDL, and file/network table functions are rejected. Responses carry at most 200 rows; aggregate in SQL, or page with ORDER BY plus LIMIT/OFFSET, rather than selecting a whole table.

Input Schema

{
  "type": "object",
  "properties": {
    "canvas_id": {
      "type": "string",
      "pattern": "^[A-Za-z0-9_-]{10}$",
      "description": "DataCanvas id returned by openaq_get_measurements โ€” minted when a series overflowed the inline preview, or the canvas_id you passed it."
    },
    "sql": {
      "type": "string",
      "description": "Read-only SELECT. Reference tables by the names openaq_get_measurements returned (e.g. measurements_1701). Use openaq_dataframe_describe first to see table and column names."
    }
  },
  "required": [
    "canvas_id",
    "sql"
  ],
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "additionalProperties": false
}

Output Schema

{
  "type": "object",
  "properties": {
    "rows": {
      "type": "array",
      "items": {
        "type": "object",
        "propertyNames": {
          "type": "string"
        },
        "additionalProperties": {}
      },
      "description": "Result rows, at most 200. Every row here is also rendered in the text output โ€” the two surfaces carry the same set."
    },
    "rowCount": {
      "type": "number",
      "description": "Rows returned in this response, always equal to rows.length. It is the cap (200) when truncated is set, not the size of the full result."
    },
    "truncated": {
      "description": "True when the query matched more than 200 rows and the response was cut to the cap. Absent when the whole result fit. Page through the rest with ORDER BY plus LIMIT/OFFSET in your own SQL.",
      "type": "boolean"
    },
    "notice": {
      "description": "How to reach the rest of the result when the row cap cut it short.",
      "type": "string"
    },
    "error": {
      "description": "Present when the call failed. Absent on success.",
      "type": "object",
      "properties": {
        "code": {
          "type": "integer",
          "minimum": -9007199254740991,
          "maximum": 9007199254740991,
          "description": "JSON-RPC error code for this failure."
        },
        "message": {
          "type": "string",
          "description": "Human-readable description of what went wrong."
        },
        "data": {
          "type": "object",
          "properties": {
            "reason": {
              "type": "string",
              "description": "Machine-readable failure mode. Declared by this tool: `canvas_unavailable`: DataCanvas is not enabled (CANVAS_PROVIDER_TYPE is not duckdb). `canvas_not_found`: The canvas_id is unknown or its canvas has expired. `missing_table`: The SQL references a table that is not staged on this canvas (dropped, expired, or misspelled). Other values are possible when a failure originates below the handler.",
              "examples": [
                "canvas_unavailable",
                "canvas_not_found",
                "missing_table"
              ]
            },
            "recovery": {
              "description": "Actionable next step for the caller.",
              "type": "object",
              "properties": {
                "hint": {
                  "type": "string"
                }
              },
              "required": [
                "hint"
              ],
              "additionalProperties": {}
            },
            "retryable": {
              "description": "Whether retrying may succeed.",
              "type": "boolean"
            }
          },
          "additionalProperties": {}
        }
      },
      "required": [
        "code",
        "message"
      ],
      "additionalProperties": {}
    }
  },
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "additionalProperties": false,
  "anyOf": [
    {
      "not": {
        "required": [
          "error"
        ]
      },
      "required": [
        "rows",
        "rowCount"
      ]
    },
    {
      "required": [
        "error"
      ]
    }
  ]
}
๐ŸŸขopenaq_dataframe_describe(canvas_id)

List the tables and columns staged on a DataCanvas so you can write valid SQL for openaq_dataframe_query without guessing column names. Returns each measurement table (measurements_<sensorId>) with its row count and column names. Requires DataCanvas to be enabled.

Input Schema

{
  "type": "object",
  "properties": {
    "canvas_id": {
      "type": "string",
      "pattern": "^[A-Za-z0-9_-]{10}$",
      "description": "DataCanvas id returned by openaq_get_measurements โ€” minted when a series overflowed the inline preview, or the canvas_id you passed it."
    }
  },
  "required": [
    "canvas_id"
  ],
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "additionalProperties": false
}

Output Schema

{
  "type": "object",
  "properties": {
    "tables": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "Table name โ€” reference it in openaq_dataframe_query SQL."
          },
          "rowCount": {
            "type": "number",
            "description": "Rows staged in this table."
          },
          "columns": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Column names available for SELECT."
          }
        },
        "required": [
          "name",
          "rowCount",
          "columns"
        ],
        "additionalProperties": false,
        "description": "A staged measurement table with its columns"
      },
      "description": "Tables currently staged on the canvas."
    },
    "notice": {
      "description": "Guidance when the canvas holds no tables yet.",
      "type": "string"
    },
    "error": {
      "description": "Present when the call failed. Absent on success.",
      "type": "object",
      "properties": {
        "code": {
          "type": "integer",
          "minimum": -9007199254740991,
          "maximum": 9007199254740991,
          "description": "JSON-RPC error code for this failure."
        },
        "message": {
          "type": "string",
          "description": "Human-readable description of what went wrong."
        },
        "data": {
          "type": "object",
          "properties": {
            "reason": {
              "type": "string",
              "description": "Machine-readable failure mode. Declared by this tool: `canvas_unavailable`: DataCanvas is not enabled (CANVAS_PROVIDER_TYPE is not duckdb). `canvas_not_found`: The canvas_id is unknown or its canvas has expired. Other values are possible when a failure originates below the handler.",
              "examples": [
                "canvas_unavailable",
                "canvas_not_found"
              ]
            },
            "recovery": {
              "description": "Actionable next step for the caller.",
              "type": "object",
              "properties": {
                "hint": {
                  "type": "string"
                }
              },
              "required": [
                "hint"
              ],
              "additionalProperties": {}
            },
            "retryable": {
              "description": "Whether retrying may succeed.",
              "type": "boolean"
            }
          },
          "additionalProperties": {}
        }
      },
      "required": [
        "code",
        "message"
      ],
      "additionalProperties": {}
    }
  },
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "additionalProperties": false,
  "anyOf": [
    {
      "not": {
        "required": [
          "error"
        ]
      },
      "required": [
        "tables"
      ]
    },
    {
      "required": [
        "error"
      ]
    }
  ]
}

Community

Rate this Server

Evidence

Recent observations

verifiedversion not recorded7 tools
verifiedversion not recorded7 tools