Skip to content

Check for eBay Deals

Watches a list of eBay searches once an hour and posts a single alert to a Discord channel when a new item falls under your price ceiling. Live searches go through eBay's official Browse API. There is also an offline fixture mode for testing without touching eBay or Discord.

Status: deployed as a Docker container and running. The live image has completed production scans and delivered alerts.

Source repository ยท Setup, configuration, and run instructions

What counts as a deal

  • US marketplace, US item location, fixed price, free shipping.
  • Price is an exact USD amount at or below the ceiling in your CSV row.
  • Parts-only listings are skipped.
  • Per scan, each search row alerts on the cheapest eligible item it has not alerted before.
  • An item alerts once, ever. A price drop on an item you already alerted on is ignored.
  • Each query reads one page of up to 200 results sorted by price. Listings outside that page are not seen.

This is a price ceiling watcher, not a discount finder. It does not know an item is a bargain, only that it is under your number.

How it runs

  • Scans about once an hour, measured from the end of the previous scan.
  • Delivery is quiet between 02:00 and 12:00 UTC. Scans keep running and new finds are stored as pending, then sent once the window closes.
  • Pending alerts are sent as they were discovered. Availability and price are not rechecked before sending.
  • Sent history and pending alerts live in a SQLite file under --data-dir, which also holds the lockfile and logs.
  • Delivery is at least once. A failed send stays pending and retries next run, so a crash in a narrow window can produce a duplicate alert.
  • A second instance sharing the same data directory exits with code 3.

Offline fixture smoke:

.venv/bin/python check_for_ebay_deals.py --once --dry-run \
  --fixture-dir tests/fixtures/offline-scan \
  --csv tests/fixtures/offline-scan/smoke.csv \
  --data-dir /tmp/deals-fixture

A dry run prints alert identities, opens state read-only, and sends nothing. Note that a dry run without fixtures still contacts eBay, so it needs API credentials. Fixture runs use a stub transport that prints deliveries instead of posting to Discord.

Configuration

Three things are needed: the search CSV (max_price,friendly_name,search_query), a Discord bot token plus channel ID, and an eBay production application credential file (App ID and Cert ID) stored as owner-only JSON with mode 0600. Docker mounts the CSV and secrets read-only and keeps state on a named volume at /data. Details and exit codes are in the README.

Known rough edges

  • If the Discord channel cannot be fetched, alerts stay pending and the next run retries, but a --once run does not report that as a failure in its exit code.
  • Corrupt or unsupported state files cause a loud startup refusal rather than a silent reset, so dedupe history is never lost by accident. Recovery is manual.