Space Data Atlas

Starter code

Short snippets that reach real NASA data. Each one that needs no login was run before publishing.

Before you start

  1. For Python, install the tools: pip install earthaccess requests.
  2. If a dataset says Earthdata Login, create a free account at urs.earthdata.nasa.gov. You only need it to download files.
  3. For a NASA API key, sign up at api.nasa.gov. Keep it in an environment variable, never in code you push.

NISAR L2 Geocoded Polarimetric Covariance (GCOV), Provisional

Search NISAR GCOV scenesPython
import earthaccess

# Search needs no login. Bounding box: (west, south, east, north)
results = earthaccess.search_data(
    short_name="NISAR_L2_GCOV_PROVISIONAL_V1",
    bounding_box=(-122.6, 37.0, -121.5, 38.2),  # San Francisco Bay
    temporal=("2025-10-01", "2026-10-04"),
    count=5,
)
print(len(results), "granules")
for g in results:
    print(g["umm"]["GranuleUR"])

# Next step (downloads need a free Earthdata Login):
# earthaccess.login()
# files = earthaccess.download(results[:1], "./nisar")

FIRMS Active Fire API

Pull fire detections for a bounding boxPython, needs login or key
import os, io
import requests
import pandas as pd

# Get a free MAP_KEY at https://firms.modaps.eosdis.nasa.gov/api/map_key/
key = os.environ["FIRMS_MAP_KEY"]
source = "VIIRS_SNPP_SP"          # or MODIS_SP for the MODIS archive
bbox = "-124.5,32.5,-114.0,42.0"  # west,south,east,north
url = f"https://firms.modaps.eosdis.nasa.gov/api/area/csv/{key}/{source}/{bbox}/5/2025-08-01"

r = requests.get(url, timeout=60)
r.raise_for_status()
df = pd.read_csv(io.StringIO(r.text))
print(len(df), "detections")
print(df[["latitude", "longitude", "acq_date", "confidence"]].head())

VIIRS/NPP Active Fires 6-Min L2 Swath 375m (VNP14IMG) V002

Find VIIRS fire swaths for a regionPython
import earthaccess

# VIIRS 375 m active fire swaths over California, one week
results = earthaccess.search_data(
    short_name="VNP14IMG",
    bounding_box=(-124.5, 32.5, -114.0, 42.0),
    temporal=("2025-08-01", "2025-08-07"),
    count=5,
)
print(len(results), "granules")
for g in results:
    print(g["umm"]["GranuleUR"])

# Swap short_name to "MOD14" for the Terra MODIS record (2000 onward).
# Downloads need Earthdata Login:
# earthaccess.login()
# earthaccess.download(results, "./fires")

Global Imagery Browse Services (GIBS)

Fetch a GIBS true-color tileJavaScript
// Build a GIBS WMTS tile URL (no key, CORS enabled) for a given day.
// Layer names come from the GIBS capabilities document.
const layer = "MODIS_Terra_CorrectedReflectance_TrueColor";
const date = "2026-10-01";
const [zoom, row, col] = [2, 1, 2];
const url = `https://gibs.earthdata.nasa.gov/wmts/epsg4326/best/${layer}` +
  `/default/${date}/250m/${zoom}/${row}/${col}.jpg`;

const res = await fetch(url);
console.log(res.status, res.headers.get("content-type"), url);
// In a map library (Leaflet, OpenLayers) use the same template with
// {z}/{y}/{x}. Fire layers such as VIIRS_SNPP_Thermal_Anomalies_375m_All
// are vector tiles (.mvt, 500m matrix set); check the capabilities XML.

SMAP Enhanced L3 Radiometer Global Daily 9 km Soil Moisture (SPL3SMP_E) V006

List daily SMAP soil moisture filesPython
import earthaccess

# Daily 9 km SMAP soil moisture over Iowa farmland
results = earthaccess.search_data(
    short_name="SPL3SMP_E",
    bounding_box=(-96.6, 40.4, -90.1, 43.5),
    temporal=("2026-06-01", "2026-06-30"),
    count=5,
)
print(len(results), "daily files")
for g in results:
    print(g["umm"]["GranuleUR"])

# earthaccess.login()  # needed to download the HDF5 files
# earthaccess.download(results, "./smap")

Harmonized Landsat Sentinel-2 (HLS) Landsat 30 m (HLSL30) v2.0

Find low-cloud HLS scenes over a farmPython
import earthaccess

# 30 m HLS Landsat scenes over a single farm area, low cloud only
results = earthaccess.search_data(
    short_name="HLSL30",
    bounding_box=(-93.7, 41.9, -93.5, 42.1),  # near Ames, Iowa
    temporal=("2026-05-01", "2026-09-30"),
    cloud_cover=(0, 20),
    count=5,
)
print(len(results), "scenes")
for g in results:
    print(g["umm"]["GranuleUR"])

# Each scene has one Cloud Optimized GeoTIFF per band.
# earthaccess.login()
# earthaccess.download(results[:1], "./hls")

NASA POWER Agroclimatology API

Season rainfall for one farmPython
import requests

# Daily temperature, rain and sunlight for one farm, no key needed
params = {
    "parameters": "T2M,PRECTOTCORR,ALLSKY_SFC_SW_DWN",
    "community": "AG",
    "latitude": 42.03, "longitude": -93.63,
    "start": "20250501", "end": "20250930",
    "format": "JSON",
}
r = requests.get("https://power.larc.nasa.gov/api/temporal/daily/point",
                 params=params, timeout=120)
r.raise_for_status()
data = r.json()["properties"]["parameter"]
rain = data["PRECTOTCORR"]
print("days:", len(rain))
print("season rainfall (mm):", round(sum(v for v in rain.values() if v >= 0), 1))
Daily temperature in the browserJavaScript
// Runs in the browser or Node 18+ (POWER sends CORS headers)
const params = new URLSearchParams({
  parameters: "T2M,PRECTOTCORR",
  community: "AG",
  latitude: "42.03",
  longitude: "-93.63",
  start: "20250701",
  end: "20250707",
  format: "JSON",
});
const res = await fetch(`https://power.larc.nasa.gov/api/temporal/daily/point?${params}`);
const json = await res.json();
const temps = json.properties.parameter.T2M;
for (const [day, t] of Object.entries(temps)) {
  console.log(day, `${t} C`);
}

GISS Surface Temperature Analysis (GISTEMP v4)

Global temperature trend per decadePython
import io
import numpy as np
import pandas as pd
import requests

url = "https://data.giss.nasa.gov/gistemp/tabledata_v4/GLB.Ts+dSST.csv"
text = requests.get(url, timeout=60).text
df = pd.read_csv(io.StringIO(text), skiprows=1, na_values="***")
df = df[["Year", "J-D"]].dropna()  # annual global anomaly, deg C

# Least-squares trend since 1980
recent = df[df["Year"] >= 1980]
slope, intercept = np.polyfit(recent["Year"], recent["J-D"], 1)
print("years:", len(df), "| latest full year:", int(df["Year"].iloc[-1]))
print(f"trend since 1980: {slope * 10:.3f} C per decade")
# For significance, use scipy.stats.linregress (p-value) or pymannkendall.

JPL GRACE and GRACE-FO Mascon Water Height, Coastal Resolution Improvement (RL06.3 v04)

Locate the GRACE mascon filePython
import earthaccess

# The JPL mascon record is one NetCDF file covering 2002 to present
results = earthaccess.search_data(
    short_name="TELLUS_GRAC-GRFO_MASCON_CRI_GRID_RL06.3_V4",
    count=5,
)
print(len(results), "file(s)")
for g in results:
    print(g["umm"]["GranuleUR"])

# earthaccess.login()
# path = earthaccess.download(results, "./grace")[0]
# import xarray as xr; print(xr.open_dataset(path))

Moon Trek WMTS: South Pole Illumination and Earth Visibility

Fetch a south pole illumination tileJavaScript
// Read the WMTS capabilities for the lunar south pole illumination layer,
// then fetch one tile. Moon Trek tiles send CORS headers.
const base = "https://trek.nasa.gov/tiles/Moon/SP/WAC_POLE_ILL_PCT_SOUTH_100M/1.0.0";
const xml = await fetch(`${base}/WMTSCapabilities.xml`).then((r) => r.text());
const template = xml.match(/template="([^"]+)"/)[1];
const matrixSet = xml.match(/<TileMatrixSet>([^<]+)<\/TileMatrixSet>/)[1];
console.log("tile template:", template);

const tileUrl = template
  .replace("{Style}", "default").replace("{TileMatrixSet}", matrixSet)
  .replace("{TileMatrix}", "0").replace("{TileRow}", "0").replace("{TileCol}", "0");
const tile = await fetch(tileUrl);
console.log(tile.status, tile.headers.get("content-type"), tileUrl);
// Earth visibility layer: swap the path to AVGVISIB_85S_060M_201608_EARTH_SP

JPL Horizons API

Sun elevation at a lunar south pole sitePython
import requests

# Sun azimuth/elevation seen from a lunar south pole site (lon 0, lat -89.5).
# Use COMMAND='399' for Earth instead of the Sun ('10').
params = {
    "format": "json",
    "COMMAND": "'10'",
    "MAKE_EPHEM": "'YES'", "EPHEM_TYPE": "'OBSERVER'", "OBJ_DATA": "'NO'",
    "CENTER": "'coord@301'", "COORD_TYPE": "'GEODETIC'",
    "SITE_COORD": "'0,-89.5,0'",  # east lon, lat, altitude km
    "START_TIME": "'2026-12-01'", "STOP_TIME": "'2026-12-03'",
    "STEP_SIZE": "'6h'", "QUANTITIES": "'4'",  # 4 = azimuth and elevation
}
r = requests.get("https://ssd.jpl.nasa.gov/api/horizons.api", params=params, timeout=60)
result = r.json()["result"]
table = result.split("$$SOE")[1].split("$$EOE")[0]
for line in table.strip().splitlines():
    print(line)

SPHEREx Quick Release Images at IRSA (TAP / SIA)

List SPHEREx visits to one sky positionPython
import io
import requests
import pandas as pd

# Find every SPHEREx image (detector D1) that covers one sky position.
# Repeat visits of the same spot are what reveal moving objects.
ra, dec = 150.1, 2.2  # COSMOS field; change to your target
query = f"""
SELECT obs_id, obs_collection, t_min, access_url
FROM spherex.obscore
WHERE CONTAINS(POINT('ICRS', {ra}, {dec}), s_region) = 1
  AND energy_bandpassname = 'SPHEREx-D1'
ORDER BY t_min
"""
r = requests.get("https://irsa.ipac.caltech.edu/TAP/sync",
                 params={"QUERY": query, "FORMAT": "csv"}, timeout=300)
df = pd.read_csv(io.StringIO(r.text))
print(len(df), "visits")  # t_min is a Modified Julian Date
print(df[["obs_id", "obs_collection", "t_min"]].head())

JPL Small-Body Database API

Look up an asteroid orbitPython
import requests

# Look up any asteroid or comet by name or designation
r = requests.get("https://ssd-api.jpl.nasa.gov/sbdb.api",
                 params={"sstr": "Bennu", "phys-par": "1"}, timeout=30)
r.raise_for_status()
d = r.json()
obj = d["object"]
elements = {e["name"]: e["value"] for e in d["orbit"]["elements"]}
print(obj["fullname"], "| class:", obj["orbit_class"]["name"])
print("semi-major axis (au):", elements["a"], "| eccentricity:", elements["e"])
print("near-Earth object:", obj["neo"], "| hazardous:", obj["pha"])

NASA Open Science Data Repository (OSDR) Search API

Search spaceflight bone-loss studiesPython
import requests

# Search public spaceflight biology studies (GeneLab + ALSDA)
params = {"term": "bone loss", "type": "cgene,alsda", "from": 0, "size": 5}
r = requests.get("https://osdr.nasa.gov/osdr/data/search", params=params, timeout=60)
r.raise_for_status()
hits = r.json()["hits"]
print("matching studies:", hits["total"])
for h in hits["hits"]:
    s = h["_source"]
    print(s.get("Accession"), "|", s.get("Study Title", "")[:80])

DONKI Space Weather Event API

Recent solar particle eventsJavaScript
// Solar energetic particle events (a radiation hazard for crews).
// No key; CORS enabled; date window must be 60 days or less.
const url = "https://ccmc.gsfc.nasa.gov/DONKI-API/get/SEP" +
  "?startDate=2026-08-06&endDate=2026-10-04";
const events = await fetch(url).then((r) => r.json());
console.log("SEP events:", events.length);
for (const e of events) {
  const instruments = e.instruments.map((i) => i.displayName).join(", ");
  console.log(e.eventTime, "|", instruments);
}
// Other event types: replace SEP with FLR (flares) or CME.

NASA Technical Reports Server (NTRS) API

Search fire safety reportsPython
import requests

# Search NASA technical reports on microgravity combustion
params = {"q": "microgravity flame spread spacecraft fire safety", "page.size": 5}
r = requests.get("https://ntrs.nasa.gov/api/citations/search", params=params, timeout=60)
r.raise_for_status()
data = r.json()
print("total matches:", data["stats"]["total"])
for rec in data["results"]:
    print(rec["id"], "|", rec["title"][:90])
    # Record page: https://ntrs.nasa.gov/citations/<id>

NASA Image and Video Library API

Search NASA photos in the browserJavaScript
// No key, CORS enabled: works directly in the browser
const q = encodeURIComponent("Apollo 15 lunar roving vehicle");
const res = await fetch(`https://images-api.nasa.gov/search?q=${q}&media_type=image`);
const { collection } = await res.json();
console.log("hits:", collection.metadata.total_hits);
for (const item of collection.items.slice(0, 5)) {
  const meta = item.data[0];
  const thumb = item.links?.[0]?.href;
  console.log(meta.nasa_id, "|", meta.title, "|", thumb);
}

NASA Scientific Visualization Studio (SVS) API

Find NASA visualizations by topicPython
import requests

# Find NASA visualizations to pair with sound
r = requests.get("https://svs.gsfc.nasa.gov/api/search/",
                 params={"search": "sea surface temperature", "limit": 5}, timeout=60)
r.raise_for_status()
data = r.json()
print("matches:", data["count"])
for item in data["results"]:
    print(item["id"], item["release_date"][:10], "|", item["title"])
    # Full media list for one entry: https://svs.gsfc.nasa.gov/api/<id>