Starter code
Short snippets that reach real NASA data. Each one that needs no login was run before publishing.
Before you start
- For Python, install the tools:
pip install earthaccess requests. - If a dataset says Earthdata Login, create a free account at urs.earthdata.nasa.gov. You only need it to download files.
- 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
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
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
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)
// 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
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
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
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))
// 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)
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)
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
// 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
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)
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
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
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
// 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
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
// 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
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>