Command-line tool and Python client for the SJVAir air quality API.
Project description
sjvair
Command-line tool and Python client for SJVAir — a network of air quality monitors across California's San Joaquin Valley.
pip install sjvair
Everything below works without an API key; the public endpoints are open. See Configuration for authenticated access and other settings.
CLI
sjvair [OPTIONS] COMMAND [ARGS]...
Options:
--version Show the version and exit.
--base-url TEXT Override API base URL (or SJVAIR_BASE_URL).
--api-key TEXT API key for authenticated requests (or SJVAIR_API_KEY).
--timeout INTEGER Request timeout in seconds (or SJVAIR_TIMEOUT).
--quiet Suppress informational output.
--force Overwrite existing output files.
-h, --help Show this message and exit.
Commands:
monitors Monitor data (list, get, entries, summaries, current, closest)
regions Region data (list, get, summaries)
calenviroscreen CalEnviroScreen 4.0 census tract scores
ceidars CEIDARS facility emissions data
hms NOAA Hazard Mapping System smoke and fire data
pesticides Pesticide use, notice, and chemical data
Common conventions
Output. Most commands print JSON to stdout by default. Pass --output PATH to write a file; the format is inferred from the extension (.csv, .json, .yaml). Force a format with --format {csv,json,yaml}. Re-running a command that would overwrite an existing file errors unless you pass the global --force.
sjvair monitors list # JSON to stdout
sjvair monitors list --output monitors.csv # CSV, format from extension
sjvair monitors list --format yaml # YAML to stdout
Region filters. Wherever a command accepts a location, these flags resolve to a region and scope the results. Use at most one:
--county · --city · --zip · --tract (FIPS) · --urban (urban-area name) · --region-id (region sqid)
Entry types are lowercase slugs: pm25, pm10, pm100, o3, no2, so2, co, co2, particulates, temperature, humidity, pressure.
monitors
list — list monitors, optionally scoped to a region or to the SJVAir-operated fleet.
sjvair monitors list
sjvair monitors list --county Fresno
sjvair monitors list --zip 93701 --output fresno-93701.csv
sjvair monitors list --is-sjvair --format yaml
get — a single monitor by ID.
sjvair monitors get 90e4a082-96b5-4bfc-8248-a1a5a2f6d0f1
entries — bulk-download raw entries over a date range (see Bulk export for how ranges are chunked). Requires --start-date, --end-date, and --output. Choose monitors by ID, from a CSV of IDs, or by region filter (defaults to all monitors when no selector is given).
# Every monitor in Fresno County for 2022, as CSV
sjvair monitors entries \
--county Fresno \
--start-date 2022-01-01 --end-date 2022-12-31 \
--output fresno-2022.csv
# Specific monitors — repeat the flag or use a comma-separated list
sjvair monitors entries \
--monitor-id uuid-1,uuid-2 \
--start-date 2023-01-01 --end-date 2023-06-30 \
--output monitors.json
# Read monitor IDs from the "id" column of a CSV
sjvair monitors entries \
--from-csv monitors.csv \
--start-date 2023-01-01 --end-date 2023-03-31 \
--output entries.csv
# Preview scope (monitors × date chunks × requests) without downloading
sjvair monitors entries --county Kern \
--start-date 2020-01-01 --end-date 2023-12-31 \
--output kern.csv --dry-run
Tuning: --period-months N (chunk size, default 5; a chunk may not exceed the server's 180-day export limit), --workers N (concurrent downloads, default 4), --scope {resolved,expanded}.
summaries — aggregated statistics per monitor. Requires --type, --resolution, --start-date, --end-date. Resolutions: hourly, daily, monthly, quarterly, seasonal, yearly. Select monitors by ID or region filter (defaults to all). Each row is tagged with its monitor_id.
# Monthly PM2.5 for every monitor in Fresno County
sjvair monitors summaries \
--type pm25 --resolution monthly \
--start-date 2022-01-01 --end-date 2022-12-31 \
--county Fresno --output monthly.csv
# Daily ozone for two specific monitors
sjvair monitors summaries \
--type o3 --resolution daily \
--monitor-id uuid-1,uuid-2 \
--start-date 2023-06-01 --end-date 2023-08-31
current — every active monitor with its latest entry for a pollutant.
sjvair monitors current --type pm25
sjvair monitors current --type o3 --output current-o3.csv
closest — up to 3 nearest active monitors to a coordinate, with distance and latest entry.
sjvair monitors closest --type pm25 --lat 36.7468 --lon -119.7726
regions
list — regions of a given --type. Types: county, city, zipcode, tract, cdp, place, urban_area, congressional_district, state_assembly, state_senate, school_district, land_use, protected, mtrs, custom.
sjvair regions list --type county
sjvair regions list --type city --county Fresno --output cities.csv
get — a single region by its sqid.
sjvair regions get gY8kw2
summaries — aggregated statistics for one region. Requires --type, --resolution, --start-date, --end-date, and exactly one region filter. Each row is tagged with its region_id.
sjvair regions summaries \
--type pm25 --resolution monthly \
--start-date 2022-01-01 --end-date 2022-12-31 \
--county Fresno --output fresno-monthly.csv
calenviroscreen
CalEnviroScreen 4.0 cumulative-impact scores by census tract for a given --year (the census year the scores are keyed to — currently 2020).
sjvair calenviroscreen --year 2020
sjvair calenviroscreen --year 2020 --county Fresno --output ces-fresno.csv
ceidars
CEIDARS facility emissions data, optionally scoped to a region.
sjvair ceidars
sjvair ceidars --county Kern --output kern-facilities.csv
hms
NOAA Hazard Mapping System smoke plumes and fire points. Both subcommands accept --date YYYY-MM-DD (defaults to today) and region filters.
sjvair hms smoke
sjvair hms fire --date 2023-08-15
sjvair hms smoke --county Fresno --output smoke.json
pesticides
California Department of Pesticide Regulation (CDPR) data. Pick a dataset with --type:
--type |
Data |
|---|---|
chemicals |
Chemical reference list |
commodities |
Commodity reference list |
products |
Product reference list |
use |
Pesticide use reports (region filter optional) |
notice |
Notice-of-intent reports (region filter optional) |
region-use |
Use aggregated for one region (region filter required) |
region-notice |
Notices aggregated for one region (region filter required) |
region-summary |
Summary totals for one region (region filter required) |
sjvair pesticides --type chemicals
sjvair pesticides --type products --output products.csv
sjvair pesticides --type use --county Fresno
sjvair pesticides --type region-summary --county Fresno
Python client
from sjvair import SJVAirClient
with SJVAirClient() as client:
# List all monitors
for monitor in client.monitors.list():
print(monitor['id'], monitor['name'])
# Get a single monitor
monitor = client.monitors.get('some-monitor-uuid')
# Fetch paginated entries
entries = list(client.monitors.entries('some-monitor-uuid', 'pm25'))
# Search regions by name, ZIP code, or FIPS tract
results = client.regions.search('Fresno')
region_id = results[0]['id']
# List monitors in a region
monitors = list(client.monitors.list(region_id=region_id))
Monitors — client.monitors
| Method | Description |
|---|---|
list(**params) |
Iterate all monitors. Filter by region_id, is_sjvair, etc. |
get(monitor_id) |
Get a single monitor by UUID. |
meta() |
Field metadata: entry types, units, level thresholds, default_pollutant. |
entries(monitor_id, entry_type, **params) |
Paginated entry records for one monitor. |
export(monitor_id, start_date, end_date, scope) |
Bulk export up to 180 days at once. |
summaries(monitor_id, entry_type, resolution, start_date, end_date) |
Aggregated summaries at hourly / daily / monthly / quarterly / seasonal / yearly resolution. Rows are tagged with monitor_id. |
closest(entry_type, lat, lon) |
Up to 3 nearest active monitors with distance and latest entry. |
current(entry_type) |
All active monitors with their most recent entry. |
Regions — client.regions
| Method | Description |
|---|---|
list(**params) |
Iterate all regions. Filter by type (county, city, zipcode, tract, …). |
get(region_id) |
Get a single region by sqid. |
search(query) |
Search by name, ZIP code, or FIPS tract code. |
summaries(region_id, entry_type, resolution, start_date, end_date) |
Aggregated summaries for a region. Rows are tagged with region_id. |
CalEnviroScreen — client.calenviroscreen
CalEnviroScreen 4.0 cumulative impact scores by census tract.
tracts = list(client.calenviroscreen.list(year=2020))
tract = client.calenviroscreen.get(year=2020, tract='06019000100')
CEIDARS — client.ceidars
California Emissions Inventory Development and Reporting System facility data.
facilities = list(client.ceidars.list())
years = client.ceidars.years()
HMS — client.hms
NOAA Hazard Mapping System smoke and fire data.
smoke = list(client.hms.smoke.list())
fires = list(client.hms.fire.list())
Pesticides — client.pesticides
California Department of Pesticide Regulation (CDPR) data.
# Reference lookups
chemicals = list(client.pesticides.chemicals.list())
commodities = list(client.pesticides.commodities.list())
products = list(client.pesticides.products.list())
# Use and notice reports
use = list(client.pesticides.use.list())
notices = list(client.pesticides.notice.list())
# Region-specific aggregates
region_use = list(client.pesticides.region_use(region_id))
summary = client.pesticides.region_summary(region_id)
Bulk export
For long date ranges, use ExportEngine — it splits the range into chunks (each within the server's 180-day export limit), downloads them concurrently, and merges the results. Interrupted runs can be resumed by re-running the same command (chunks that already have staging files are skipped):
from pathlib import Path
from sjvair import SJVAirClient
from sjvair.export.engine import ExportEngine
with SJVAirClient() as client:
engine = ExportEngine(client, output=Path('fresno-pm25.csv'))
engine.run(monitor_ids=['uuid-1', 'uuid-2'], start_date='2020-01-01', end_date='2023-12-31')
Output formats
format_output(data, fmt) converts any record iterator:
| Format | Returns |
|---|---|
'objects' |
The iterator unchanged |
'tabular' |
(headers: list[str], rows: Iterator[list]) |
'dataframe' |
pandas.DataFrame — requires pip install sjvair[maps] |
'geodataframe' |
geopandas.GeoDataFrame with geometry parsed — requires pip install sjvair[maps] |
Configuration
All settings can be passed as constructor arguments or set via environment variables (.env files are loaded automatically by the CLI):
| Argument | Environment variable | Default |
|---|---|---|
base_url |
SJVAIR_BASE_URL |
https://www.sjvair.com/api/2.0/ |
api_key |
SJVAIR_API_KEY |
(none — public endpoints work without a key) |
timeout |
SJVAIR_TIMEOUT |
30 seconds |
Development
# Install with dev dependencies
uv sync --group dev
# Run tests (live tests hit the real API and are excluded by default)
uv run pytest
uv run pytest -m live # run the live integration tests
# Lint and format
uv run ruff check sjvair/
uv run ruff format sjvair/
# Type check
uv run ty check sjvair/
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sjvair-0.1.0a1.tar.gz.
File metadata
- Download URL: sjvair-0.1.0a1.tar.gz
- Upload date:
- Size: 22.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f6c1efe3aeaaeec2622976f0a87e3e859507fe5e491b22fe6d5126ef68a3704e
|
|
| MD5 |
06c22370823536835969a8d5689fc15d
|
|
| BLAKE2b-256 |
5e1500fa06e3222f88f2827524a34a0d364d72e4de115408110ff72416f069d4
|
Provenance
The following attestation bundles were made for sjvair-0.1.0a1.tar.gz:
Publisher:
publish.yml on SJVAir/sjvair-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sjvair-0.1.0a1.tar.gz -
Subject digest:
f6c1efe3aeaaeec2622976f0a87e3e859507fe5e491b22fe6d5126ef68a3704e - Sigstore transparency entry: 2054344601
- Sigstore integration time:
-
Permalink:
SJVAir/sjvair-python@275104600636b51b63d9837dba3a1028a74cc636 -
Branch / Tag:
refs/tags/v0.1.0a1 - Owner: https://github.com/SJVAir
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@275104600636b51b63d9837dba3a1028a74cc636 -
Trigger Event:
push
-
Statement type:
File details
Details for the file sjvair-0.1.0a1-py3-none-any.whl.
File metadata
- Download URL: sjvair-0.1.0a1-py3-none-any.whl
- Upload date:
- Size: 34.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
70709f94e69a04af9fe5b26cdecbfadae37c89b81c3dc02583b2ce392e04df43
|
|
| MD5 |
abe36d8065e2b43afb95f271205fd8cf
|
|
| BLAKE2b-256 |
2b423e29bc6077795c7310fb2dd7de5de56b591d2fde65da57ccb14845f2e4da
|
Provenance
The following attestation bundles were made for sjvair-0.1.0a1-py3-none-any.whl:
Publisher:
publish.yml on SJVAir/sjvair-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sjvair-0.1.0a1-py3-none-any.whl -
Subject digest:
70709f94e69a04af9fe5b26cdecbfadae37c89b81c3dc02583b2ce392e04df43 - Sigstore transparency entry: 2054345210
- Sigstore integration time:
-
Permalink:
SJVAir/sjvair-python@275104600636b51b63d9837dba3a1028a74cc636 -
Branch / Tag:
refs/tags/v0.1.0a1 - Owner: https://github.com/SJVAir
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@275104600636b51b63d9837dba3a1028a74cc636 -
Trigger Event:
push
-
Statement type: