# Changelog of NASA APOD Scraper (`devilscrapes/nasa-apod-scraper`) Actor

- **URL**: https://apify.com/devilscrapes/nasa-apod-scraper/changelog.md
- **Full Actor documentation**: https://apify.com/devilscrapes/nasa-apod-scraper.md

## NASA APOD Scraper — Changelog

### 0.6 — 2026-09-09

- Fix: NASA meters `DEMO_KEY` per source IP, so a spent quota is a
  property of the exit, not of the run. The scraper now asks Apify Proxy
  for a fresh exit and retries (up to 3 exits) before declaring the quota
  exhausted, and `proxyConfiguration.useApifyProxy` now defaults to **true**
  so every run starts on its own exit instead of the shared Apify egress
  every other keyless customer burns. Customer-supplied `apiKey`s are
  metered per key and never rotate. Regression tests use fake sessions.
- Fix: a persistent NASA `429 OVER_RATE_LIMIT` used to FAILED the whole run.
  `_get_with_retry` ignored the `Retry-After` header entirely and retried a
  fixed exponential schedule (capped at 30s) for 5 attempts, then raised
  `RuntimeError`, which propagated uncaught out of `scraper.run` and failed
  the Actor. The shared `DEMO_KEY` quota (30 req/hour, 50/day) is drawn
  against by every customer who never sets their own `apiKey` — this
  Actor's default input (`proxyConfiguration.useApifyProxy: false`) hits
  `api.nasa.gov` directly from Apify's own IPs, no proxy rotation. A live
  probe against the real API confirmed the failure mode: once the quota is
  spent, NASA returns `429` with `Retry-After: 14609` (~4h) — no retry
  schedule inside a single run can wait that out, and the old code burned
  all 5 attempts pointlessly before failing loud anyway. Reproduced live
  with `apify run` from a rate-limited local IP: pre-fix, `exit_code: 91`
  (`RuntimeError: nasa apod failed after 5 retries`); post-fix,
  `exit_code: 0` with `Done — 0 item(s) scraped. NASA API rate limit hit —
  ...`. Now `_wait_before_retry` honours `Retry-After` for genuinely
  transient throttling, and raises a new `QuotaExhaustedError` immediately
  (no wasted retries) when the requested wait exceeds
  `RATE_LIMIT_GIVE_UP_THRESHOLD_S` (60s) — `main.py` catches that
  specifically and finishes the run SUCCEEDED with whatever was already
  pushed plus a status message telling the customer to supply their own
  `apiKey` for a higher, per-customer quota.

### 0.5 — 2026-09-03

- Fix: `mode=range` no longer requires `startDate`/`endDate` up front. Those
  fields only carry a Console-UI `prefill` in `input_schema.json`, not a
  schema `default` — Apify merges `default` into API-triggered runs but not
  `prefill`. Any customer calling the Actor via the API with a minimal
  payload (including `{}`, since `mode` itself defaults to `"range"`) hit
  an unhandled `ValueError: mode=range requires startDate and endDate`.
  This was the dominant failure signature behind the 30-day fleet health
  check (65% success, 10/29 runs FAILED) — confirmed on the actor's own
  account run `TyQJkdKvpWpAQg6Rk` (`{"mode": "range", "count": 5,
  "thumbsForVideos": true}`). Range mode without explicit dates now falls
  back to the last `count` days ending today, the same way `mode=random`
  already uses `count`. A *one-sided* range (only one of the two dates
  set) still fails loud — that's a genuine customer mistake, not a missing
  default.

### 0.1.0 — 2026-05-15

- Initial scaffolded release.
