Search eBay's live active listings by keyword. Get title, price, condition, buying format, item URL, and thumbnail per card. Filter by category, condition, price range, and buying format. We handle the warm-up cookies and retries so you get clean rows every run.
v0.1.0 — Fix: empty search is a success, not a FAILED run (2026-09-09)
Fixed
A zero-row search no longer FAILs the run. 65% customer success rate
(7/20 FAILED runs, 30d) traced to main._finish() unconditionally
raise SystemExit(1) whenever a run emitted zero rows (spec REQ-9,
"fail loud on zero rows") — charging the customer actor-start and then
reporting FAILED for what could be an ordinary empty search. See
ops/os/EMPTY-IS-NOT-A-FAILURE-2026-08-19.md; this Actor was built
2026-08-13/31, after that fleet-wide finding, and slipped through
un-retrofitted. _finish() now finishes SUCCEEDED with a status message
naming the query/filters on zero rows.
Root cause of the zero rows themselves: an undetected 200-status block.
Live local recon (2026-09-09, via Apify proxy and direct) reproduced eBay
serving a 200 response whose body carries none of the site's own
srp-results search-results markup — a soft block/interstitial dressed as
success. The old code handed that body straight to parser.parse_cards(),
which correctly found zero cards on it (there was nothing to find), and
that read identically to a genuine no-match query. EbaySession.fetch_search_page
now checks parser.is_recognizable_results_page(html) on every 200 and, if
the body isn't recognizable, rotates session/profile and re-warms-up
exactly like a 403 challenge (REQ-7), raising EbayRequestError only after
retries are exhausted. Reproduced live: the QA fixture run hit this exact
path on its very first attempt, rotated once, and then emitted 5 real rows.
Retitled the zero-rows status message ("Found 0 listings for query=…") since
it can no longer be an ambiguous block-vs-no-match message — every block
path raises before _finish is reached now.
Fixed a stale test (test_pay_per_event_matches_spec_exactly) asserting the
actor-start fee was still $0.05; pay_per_event.json already carries the
fleet-standard $0.20 flat start fee, only the test hadn't been updated.