Downloading TEMPO Mission Data as CSVs

Published

April 20, 2026

Overview

Welcome to this tutorial on accessing data from the TEMPO (Tropospheric Emissions: Monitoring of Pollution) satellite mission. TEMPO is NASA’s first Earth Venture Instrument mission to monitor air pollutants across North America on an hourly basis from geostationary orbit.

While TEMPO data is natively stored in netCDF format, many workflows and analysis tools require tabular data. Using NASA’s Harmony API, we can request TEMPO data, spatially subset it to our region of interest, and automatically convert it into CSV format—all in one step.

By the end of this notebook, you will have downloaded a .zip file containing ready-to-use CSV files of TEMPO Nitrogen Dioxide (NO2) data, along with a Readme outlining the file contents and data attributes.

Prerequisites

Harmony-py:

Make sure the following packages are installed in your environment:

pip install harmony-py 

harmony-py is a package that allows for easy interaction with the Harmony API directly in Python.

Authentication:

The harmony.Client class will attempt to use credentials from a local .netrc file, located in the home directory of the filesystem where this notebook is running. Ensure your .netrc file contains Earthdata credentials for your Earthdata Login:

machine urs.earthdata.nasa.gov
    login <edl_username>
    password <edl_password>

Notebook Author / Affiliation

Atmospheric Science Data Center (ASDC)

Set up the output directory

Set up a local directory named TEMPO-CSV-output where the requested data will be downloaded and extracted.

from pathlib import Path
from os import listdir
from shutil import rmtree
from zipfile import ZipFile

from harmony import BBox, Client, Collection, Request, Environment

# Create output directory. If already exists, remove and create clean directory
output_dir = Path("./TEMPO-CSV-output")
try:
    output_dir.mkdir()
except FileExistsError:
    rmtree(output_dir)
    output_dir.mkdir()

op_dirs = []

Initialize the Harmony client

Initialize the Harmony Client to interact with the NASA Earthdata production environment.

harmony_client = Client(env=Environment.PROD)

Define your TEMPO data request

Set up a Harmony Request specifying the TEMPO NO2 Level-2 (L2) collection. We will apply a spatial bounding box to subset the data to our region of interest, and crucially, request the output format as text/csv to trigger the automated NetCDF-to-CSV conversion.

This example requests data over a region spanning roughly from eastern Utah to western Kansas (~Colorado). Adjust the bounding box coordinates to your own area of interest.

collection = Collection(id="C2930725014-LARC_CLOUD")  # TEMPO_NO2_L2
request = Request(
    collection=collection,
    spatial=BBox(-111, 39, -101, 40),  # Restrict data to this bounding box
    max_results=2,  # Small for demo purposes; increase as needed
    extend="mirror_step",  # TEMPO data dimension name – required for multi-granule processing
    format="text/csv",  # Request the data as CSV instead of native NetCDF
)

# Validate the request before submitting
print(f"Request is valid: {request.is_valid()}")
# Submit the request and wait for processing to complete
job_id = harmony_client.submit(request)

Download the output files and extract the contents

Download the output files, store them in the output directory TEMPO-CSV-output, and then extract the contents of the downloaded zip files to see the resulting CSVs.

# Download the zip file
downloaded_files = [
    f.result() for f in harmony_client.download_all(job_id, directory=output_dir, overwrite=True)
]

for zfile in downloaded_files:
    # Extract files from zip
    extract_dir = zfile.split(".")[0]
    op_dirs.append(extract_dir)
    with ZipFile(zfile, "r") as zip_ref:
        zip_ref.extractall(extract_dir)

    op_files = sorted(listdir(extract_dir))
    files = "\n  ".join(op_files)
    print(f"\nFiles from {zfile.split('/')[-1]} conversion:\n ", files)

Examine the Readme markdown file

Look at the generated Readme file to examine the native NetCDF global attributes and see exactly which scientific variables each .csv file contains.

from IPython.display import display, Markdown

for d in op_dirs:
    with open(f"{d}/Readme.md", "r", encoding="utf-8") as f:
        content = f.read()
        display(Markdown(content))