Domain skill
nasa
Markdown synced from browser-harness domain skills.
- Host
- nasa
- Files
- 1
Agent prompt
Use this skill
Copy this prompt into your coding agent to make it enable browser-harness domain skills and read this exact domain folder before automating.
Set up https://github.com/browser-use/browser-harness for me if it is not already installed. If setup is needed, read `install.md` first to install and connect it to my real browser. Then read `SKILL.md` for normal usage and always read `helpers.py` because that is where the browser-harness functions are. Enable domain skills if they are not already enabled by setting `BH_DOMAIN_SKILLS=1` for browser-harness. Use the `nasa` domain skill from `agent-workspace/domain-skills/nasa/`. Read every markdown file for this domain before inventing an approach: - agent-workspace/domain-skills/nasa/scraping.md Use those domain-skill notes to complete my task for `nasa` in my real browser. When you open a setup, verification, or task tab, activate it so I can see the active browser tab.
Skill contents
What the agent will read
Scraping & Data Extraction
scraping.md
- https://api.nasa.gov — open NASA data APIs. Never use the browser. All endpoints return JSON via httpget. DEMOKEY works for low-volume use; register for a free personal key at https://api.nasa.gov/ to raise limits.
- All api.nasa.gov endpoints share the same rate-limit pool under DEMOKEY. EPIC and Exoplanet Archive are on separate domains with no rate limit.
- Use DEMOKEY for exploration. Switch to a personal key for any bulk work — DEMOKEY hits its limit at 10 req/hour/IP (daily budget around 50; retry-after header will show 22 hours when exhausted).
- Rate limit headers on every api.nasa.gov response:
- X-Ratelimit-Limit — your current window limit (e.g. 10)
Show full markdown
https://api.nasa.gov — open NASA data APIs. Never use the browser. All endpoints return JSON via http_get. DEMO_KEY works for low-volume use; register for a free personal key at https://api.nasa.gov/ to raise limits.
Do this first
All api.nasa.gov endpoints share the same rate-limit pool under DEMO_KEY. EPIC and Exoplanet Archive are on separate domains with no rate limit.
import json
from helpers import http_get
# Simplest call: today's Astronomy Picture of the Day
apod = json.loads(http_get("https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY"))
print(apod['date'], apod['title'], apod['media_type'])
# Confirmed output (2026-04-18): 2026-04-18 PanSTARRS and Planets image
Use DEMO_KEY for exploration. Switch to a personal key for any bulk work — DEMO_KEY hits its limit at ~10 req/hour/IP (daily budget around 50; retry-after header will show ~22 hours when exhausted).
Rate limits
| Key type | Limit | Resets |
|---|---|---|
DEMO_KEY | 10 req/hour, ~50/day per IP | Hourly window; daily hard stop with retry-after ~22h |
| Personal key (free) | 1,000 req/hour | Hourly window |
Rate limit headers on every api.nasa.gov response:
X-Ratelimit-Limit— your current window limit (e.g.10)X-Ratelimit-Remaining— calls left this windowRetry-After— seconds until next window (only on 429)
EPIC (epic.gsfc.nasa.gov) and Exoplanet Archive (exoplanetarchive.ipac.caltech.edu) share no rate-limit pool with api.nasa.gov.
Common workflows
APOD — single day
import json
from helpers import http_get
apod = json.loads(http_get("https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY"))
print(apod['date']) # '2026-04-18'
print(apod['title']) # 'PanSTARRS and Planets'
print(apod['media_type']) # 'image' or 'video'
print(apod['url']) # full-res or YouTube embed URL
print(apod['hdurl']) # HD image URL (absent when media_type='video')
print(apod.get('copyright')) # None if public domain
# Confirmed output (2026-04-18):
# url: https://apod.nasa.gov/apod/image/2604/PanstarrsPlanetsPerrotLab1024.jpg
# hdurl: https://apod.nasa.gov/apod/image/2604/PanstarrsPlanetsPerrot.jpg
# copyright: Luc Perrot
APOD — date range (array response)
import json
from helpers import http_get
apods = json.loads(http_get(
"https://api.nasa.gov/planetary/apod"
"?start_date=2024-01-01&end_date=2024-01-07&api_key=DEMO_KEY"
))
# Returns a list of 7 dicts — same schema as single-day response
for a in apods:
print(a['date'], a['media_type'], a['title'][:50])
# Confirmed output (7 items):
# 2024-01-01 image NGC 1232: A Grand Design Spiral Galaxy
# 2024-01-02 image Rocket Transits Rippling Moon
# 2024-01-03 image A SAR Arc from New Zealand
# 2024-01-04 image Zeta Oph: Runaway Star
# 2024-01-05 image Trapezium: At the Heart of Orion
# 2024-01-06 video The Snows of Churyumov-Gerasimenko
# 2024-01-07 image The Cat's Eye Nebula in Optical and X-ray
Optional params: date=YYYY-MM-DD (specific day), count=N (N random entries), thumbs=true (include thumbnail_url for video entries).
APOD — random sample
import json
from helpers import http_get
apods = json.loads(http_get(
"https://api.nasa.gov/planetary/apod?count=5&api_key=DEMO_KEY"
))
for a in apods:
print(a['date'], a['title'][:40])
# Returns 5 random APOD entries — dates can be any day since 1995-06-16
NEO — Near Earth Objects feed
import json
from helpers import http_get
data = json.loads(http_get(
"https://api.nasa.gov/neo/rest/v1/feed"
"?start_date=2024-01-01&end_date=2024-01-02&api_key=DEMO_KEY"
))
print(data['element_count']) # 32 (total NEOs across both days)
neos = data['near_earth_objects'] # dict keyed by date string
for date, objects in sorted(neos.items()):
for neo in objects:
ca = neo['close_approach_data'][0]
print(
neo['name'],
'hazardous:', neo['is_potentially_hazardous_asteroid'],
'miss km:', ca['miss_distance']['kilometers'][:12],
'vel kph:', ca['relative_velocity']['kilometers_per_hour'][:10]
)
# Confirmed output (2 days, 32 total NEOs):
# 415949 (2001 XY10) hazardous: False miss km: 50452409.34 vel kph: 57205.8951
# (22+ more objects per day)
NEO object fields:
id,name,nasa_jpl_url— identityestimated_diameter— dict withkilometers,meters,miles,feetsub-dicts, each withmin/maxis_potentially_hazardous_asteroid— boolclose_approach_data[0]—close_approach_date,miss_distance(au/lunar/km/mi),relative_velocity(km/s, km/h, mph),orbiting_body
Date range is capped at 7 days per request. For longer ranges, paginate with start_date / end_date in 7-day steps. links.next in the response gives the next 7-day window URL.
NEO — single asteroid lookup
import json
from helpers import http_get
# Asteroid ID comes from the feed's `id` field
neo = json.loads(http_get(
"https://api.nasa.gov/neo/rest/v1/neo/2415949?api_key=DEMO_KEY"
))
print(neo['name'])
print(neo['orbital_data']['orbit_class']['orbit_class_description'])
# Full orbital history + all close approaches are in `close_approach_data` (long list)
Mars Rover photos — Curiosity by sol
import json
from helpers import http_get
# sol = Martian solar day since landing
data = json.loads(http_get(
"https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos"
"?sol=1000&api_key=DEMO_KEY"
))
photos = data['photos']
print(f"Photos on sol 1000: {len(photos)}")
p = photos[0]
print(p['earth_date']) # '2015-05-30'
print(p['img_src']) # direct JPEG URL
print(p['camera']['name']) # 'FHAZ'
print(p['camera']['full_name']) # 'Front Hazard Avoidance Camera'
print(p['rover']['name']) # 'Curiosity'
print(p['rover']['status']) # 'active'
print(p['rover']['max_sol']) # highest sol with photos
# Filter by camera
data = json.loads(http_get(
"https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos"
"?sol=1000&camera=navcam&api_key=DEMO_KEY"
))
Available cameras for Curiosity: fhaz, rhaz, mast, chemcam, mahli, mardi, navcam. Other rovers: opportunity, spirit, perseverance.
Use latest_photos to get the most recent available:
data = json.loads(http_get(
"https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/latest_photos"
"?api_key=DEMO_KEY"
))
photos = data['latest_photos']
Add &page=N for pagination (25 photos/page by default).
EPIC — Earth Polychromatic Imaging Camera
EPIC images are served from epic.gsfc.nasa.gov — no api_key required, no rate limit.
import json
from helpers import http_get
# Latest available images (natural color)
images = json.loads(http_get("https://epic.gsfc.nasa.gov/api/natural"))
print(f"Latest batch: {len(images)} images") # Confirmed: 4 images on 2026-04-18
img = images[0]
print(img['identifier']) # '20260416162050'
print(img['image']) # 'epic_1b_20260416162050'
print(img['date']) # '2026-04-16 16:16:01'
print(img['centroid_coordinates']) # {'lat': 13.25, 'lon': -75.59}
# Construct PNG URL from image name + date
date_str = img['date'].split(' ')[0] # '2026-04-16'
year, month, day = date_str.split('-')
png_url = f"https://epic.gsfc.nasa.gov/archive/natural/{year}/{month}/{day}/png/{img['image']}.png"
jpg_thumb = f"https://epic.gsfc.nasa.gov/archive/natural/{year}/{month}/{day}/thumbs/{img['image']}.jpg"
print(png_url)
# Confirmed: https://epic.gsfc.nasa.gov/archive/natural/2026/04/16/png/epic_1b_20260416162050.png
# Images for a specific date
images = json.loads(http_get("https://epic.gsfc.nasa.gov/api/natural/date/2024-01-15"))
print(len(images)) # 14 images on 2024-01-15
# Enhanced (color-corrected) images — same API, different path
enhanced = json.loads(http_get("https://epic.gsfc.nasa.gov/api/enhanced/date/2024-01-15"))
# Enhanced image URL pattern uses 'enhanced' and 'epic_RGB_' prefix:
img = enhanced[0]
date_str = img['date'].split(' ')[0]
year, month, day = date_str.split('-')
url = f"https://epic.gsfc.nasa.gov/archive/enhanced/{year}/{month}/{day}/png/{img['image']}.png"
# e.g. .../archive/enhanced/2024/01/15/png/epic_RGB_20240115005515.png
# All available dates
all_dates = json.loads(http_get("https://epic.gsfc.nasa.gov/api/natural/all"))
print(f"Available dates: {len(all_dates)}") # 3477 dates (2015-06-13 to present)
print(all_dates[0]) # {'date': '2026-04-16'} (newest first)
print(all_dates[-1]) # {'date': '2015-06-13'} (oldest)
Exoplanet Archive — TAP/ADQL queries
No API key or rate limit. SQL-like ADQL queries over the full archive.
import json
from helpers import http_get
# Short-period planets with known radii
planets = json.loads(http_get(
"https://exoplanetarchive.ipac.caltech.edu/TAP/sync"
"?query=select+pl_name,hostname,pl_orbper+from+ps+where+pl_orbper+%3C+10"
"&format=json"
))
print(f"Rows: {len(planets)}") # 17675 (table 'ps' includes duplicate measurements)
print(planets[0])
# {'pl_name': 'GJ 1214 b', 'hostname': 'GJ 1214', 'pl_orbper': 1.58040482}
# Use 'pscomppars' for one row per planet (composite best-estimate params)
planets = json.loads(http_get(
"https://exoplanetarchive.ipac.caltech.edu/TAP/sync"
"?query=select+pl_name,hostname,disc_year,discoverymethod,pl_orbper,pl_rade,pl_masse,pl_eqt,sy_dist"
"+from+pscomppars+where+disc_year+%3E+2020+and+pl_rade+is+not+null"
"+order+by+disc_year+desc"
"&format=json&maxrec=5"
))
for p in planets:
print(p['pl_name'], p['disc_year'], p['discoverymethod'], f"r={p['pl_rade']}Re")
# Confirmed output:
# KMT-2024-BLG-1870L b 2026 Microlensing r=13.8Re
# LHS 1903 b 2026 Transit r=1.382Re
# TOI-375 d 2026 Radial Velocity r=13.6Re
Key tables:
ps— all measurements per planet (multiple rows per planet, all sources)pscomppars— one row per confirmed planet (best composite parameters)
Key columns: pl_name, hostname, disc_year, discoverymethod, pl_orbper (orbital period, days), pl_rade (radius in Earth radii), pl_masse (mass in Earth masses), pl_eqt (equilibrium temp K), sy_dist (distance in parsec).
URL-encode operators: < = %3C, > = %3E, spaces = +.
URL reference
api.nasa.gov endpoints
| Endpoint | URL pattern |
|---|---|
| APOD today | https://api.nasa.gov/planetary/apod?api_key=KEY |
| APOD by date | ...&date=YYYY-MM-DD |
| APOD range | ...&start_date=YYYY-MM-DD&end_date=YYYY-MM-DD |
| APOD random N | ...&count=N |
| NEO feed | https://api.nasa.gov/neo/rest/v1/feed?start_date=...&end_date=...&api_key=KEY |
| NEO by ID | https://api.nasa.gov/neo/rest/v1/neo/{id}?api_key=KEY |
| Mars photos by sol | https://api.nasa.gov/mars-photos/api/v1/rovers/{rover}/photos?sol=N&api_key=KEY |
| Mars photos by date | ...?earth_date=YYYY-MM-DD&api_key=KEY |
| Mars latest | https://api.nasa.gov/mars-photos/api/v1/rovers/{rover}/latest_photos?api_key=KEY |
EPIC (epic.gsfc.nasa.gov — no key, no rate limit)
| Endpoint | URL |
|---|---|
| Latest natural images | https://epic.gsfc.nasa.gov/api/natural |
| Natural by date | https://epic.gsfc.nasa.gov/api/natural/date/YYYY-MM-DD |
| Enhanced latest | https://epic.gsfc.nasa.gov/api/enhanced |
| Enhanced by date | https://epic.gsfc.nasa.gov/api/enhanced/date/YYYY-MM-DD |
| All available dates | https://epic.gsfc.nasa.gov/api/natural/all |
| PNG image | https://epic.gsfc.nasa.gov/archive/natural/YYYY/MM/DD/png/{image}.png |
| Thumbnail (JPEG) | https://epic.gsfc.nasa.gov/archive/natural/YYYY/MM/DD/thumbs/{image}.jpg |
| Enhanced PNG | https://epic.gsfc.nasa.gov/archive/enhanced/YYYY/MM/DD/png/{image}.png |
Exoplanet Archive (no key, no rate limit)
https://exoplanetarchive.ipac.caltech.edu/TAP/sync?query=<ADQL>&format=json&maxrec=<N>
Gotchas
-
DEMO_KEY limit is effectively 10/hour per IP, not 30 — The
X-Ratelimit-Limitheader shows10in practice. When the daily budget (~50 req) is exhausted, theretry-afterheader is set to ~80,000 seconds (about 22 hours). Register a free personal key at https://api.nasa.gov/ to get 1,000/hour. -
All
api.nasa.govpaths share one rate-limit pool — APOD, NEO, Mars Rover, and all otherapi.nasa.govpaths draw from the same DEMO_KEY bucket. Calling any one of them depletes the limit for all others. -
EPIC and Exoplanet Archive are fully free —
epic.gsfc.nasa.govreturns no rate-limit headers and is not throttled.exoplanetarchive.ipac.caltech.edu/TAP/syncis similarly unrestricted. Use these freely without fear of exhausting DEMO_KEY. -
NEO date range max is 7 days — Requests spanning more than 7 days return HTTP 400. Paginate with 7-day windows and use
links.nextfrom the response to get the next URL. -
APOD earliest date is 1995-06-16 — Requesting
datebefore1995-06-16returns HTTP 400 with an error message. No upper date bound other than today. -
APOD
hdurlis absent for video entries — Whenmedia_typeisvideo, the response hasurl(a YouTube embed URL) but nohdurl. Always checkmedia_typebefore accessinghdurl. -
Mars Rover
solvsearth_date— Both are valid filter params.solis the Martian solar day since rover landing.earth_dateusesYYYY-MM-DD. You cannot mix them in one request. -
Mars Rover pagination defaults to 25 photos/page — Large sols (Curiosity sol 1000 has many photos) require
&page=2,&page=3, etc. There is no total count in the response; keep paginating until you get an emptyphotoslist. -
EPIC image name encodes type in the prefix — Natural images use
epic_1b_prefix; enhanced color-corrected images useepic_RGB_prefix. The API returns the correct filename inimg['image']; don't guess the prefix. -
EPIC
/api/natural/allreturns newest-first — The list of 3,477+ available dates starts from today and goes back to 2015-06-13. Not all days have images (gaps during spacecraft maintenance). -
Exoplanet
pstable has multiple rows per planet — Different publications report different measurements for the same planet. Usepscompparsfor one-row-per-planet composite parameters.psis useful when you need all reported values or want to filter by specific reference. -
Exoplanet null values come back as
Nonein JSON — Many fields likepl_massearenullfor planets without mass measurements. Always guard withif row['pl_masse'] is not None. -
http_getin helpers.py uses stdliburllib— On some macOS Python 3.11 installs, SSL certificate verification fails (CERTIFICATE_VERIFY_FAILED). If you hit this, runcurlviasubprocessas a fallback, or install certifi and patch the default SSL context. The harness's browser CDP connection is not affected; onlyhttp_getis.