Building a Price-Tracking Agent with eBay's Search Filters
Track a product on eBay with Serply's filter aliases and alert yourself the moment a listing drops under your target price.

- Why eBay search needs filters, not just keywords
- The endpoint and how the query string works
- The alias table
- Building the tracker
- Wiring it into an alert loop
- Sorting matters more than you’d think
- Where this goes from here
Everyone has that one item they’re watching. A specific laptop model, a discontinued camera lens, a graphics card that’s finally come back down to earth. You could refresh the eBay search page fifty times a day, or you could write forty lines of Python that do it for you and only bother you when the price actually hits your number.
That’s what we’re building here: a small agent that queries eBay through the Serply eBay Search API, filters for exactly the kind of listing you want, parses out the price, and flags anything under your threshold. No headless browser, no scraping eBay’s HTML yourself, no getting your IP flagged for hitting /sch/i.html too many times.
Why eBay search needs filters, not just keywords
A plain keyword search on eBay is almost useless for price tracking. Search “RTX 4070” and you’ll get new cards, used cards, empty boxes, broken cards being sold for parts, and a scattering of auctions ending in six days. If you want “used or refurbished, Buy It Now, under $450, cheapest first,” you need eBay’s actual filter parameters — and eBay’s parameter names are not exactly self-explanatory. LH_ItemCondition=3000 means “used.” _sop=15 means “sort by price, low to high.” Nobody remembers that on their own.
Serply’s eBay endpoint solves this with a set of friendly aliases that sit on top of eBay’s real parameters. You write condition=used and sort=price_asc, Serply translates them to LH_ItemCondition=3000 and _sop=15 before the request ever reaches eBay. If you already know eBay’s raw parameter names, those still work too — the aliases are additive, not a replacement.
The endpoint and how the query string works
GET https://api.serply.io/v1/ebay/search/{query}
The important thing to know about this API family is that the query string isn’t passed as normal ?key=value parameters after the path — it’s packed directly into the URL path itself. So a filtered search looks like this:
GET https://api.serply.io/v1/ebay/search/q=rtx+4070&min_price=200&max_price=450&buy_now=1&condition=used&sort=price_asc
Every request needs the X-Api-Key header. See the authentication guide if you haven’t set that up yet.
The alias table
Here’s the full set of friendly aliases Serply supports for eBay search, and the real eBay parameter each one maps to:
| Alias | Real eBay param | Values |
|---|---|---|
min_price | _udlo | number (USD, no $) |
max_price | _udhi | number (USD, no $) |
buy_now | LH_BIN=1 | 1 / true / yes / on |
auction | LH_Auction=1 | 1 / true / yes / on |
best_offer | LH_BO=1 | 1 / true / yes / on |
free_shipping | LH_FS=1 | 1 / true / yes / on |
sold | LH_Sold=1 | 1 / true / yes / on |
completed | LH_Complete=1 | 1 / true / yes / on |
returns_accepted | LH_ReturnsAccepted=1 | 1 / true / yes / on |
local_pickup | LH_LPickup=1 | 1 / true / yes / on |
condition | LH_ItemCondition | new (1000), open_box (1500), manufacturer_refurbished (2000), seller_refurbished (2500), used (3000), for_parts (7000) |
sort | _sop | best_match (12), price_asc (15), price_desc (16), newest (10), ending_soonest (1), distance_nearest (7) |
The boolean aliases (buy_now, auction, best_offer, and so on) also accept 0/false if you want to explicitly turn a filter off. And if you need something not on this list — a specific category ID via _sacat, for example — you can pass eBay’s raw parameter name directly in the same query string. Aliases and raw params happily coexist.
Building the tracker
The plan is simple:
- Query eBay for the product, filtered to the condition and listing type you actually want.
- Sort by price ascending, so the cheapest matching listing is first.
- Parse each listing’s price out of the string eBay/Serply hands back (
"$412.50", not412.50). - Compare against your target price and flag anything under it.
Here’s the core query function:
import requests
API_KEY = "YOUR_API_KEY"
BASE_URL = "https://api.serply.io/v1/ebay/search"
def search_ebay(query, min_price=None, max_price=None, condition=None,
buy_now=None, sort="price_asc"):
parts = [f"q={query}"]
if min_price is not None:
parts.append(f"min_price={min_price}")
if max_price is not None:
parts.append(f"max_price={max_price}")
if condition:
parts.append(f"condition={condition}")
if buy_now is not None:
parts.append(f"buy_now={1 if buy_now else 0}")
if sort:
parts.append(f"sort={sort}")
path = "&".join(parts)
url = f"{BASE_URL}/{path}"
response = requests.get(url, headers={"X-Api-Key": API_KEY})
response.raise_for_status()
return response.json()
Note that query here isn’t URL-encoded for you — replace spaces with + before calling this ("rtx+4070", not "rtx 4070"), same as every other Serply search endpoint.
Now the part that actually matters for price tracking: turning "$412.50" into 412.50 so you can do math on it. The price field comes back exactly as eBay displays it — a string, with a currency symbol, and sometimes a thousands separator — so don’t try to float() it directly.
import re
def parse_price(price_str):
"""Turn a displayed price like '$1,249.00' into a float."""
if not price_str:
return None
cleaned = re.sub(r"[^\d.]", "", price_str)
try:
return float(cleaned)
except ValueError:
return None
def find_deals(query, target_price, condition="used", buy_now=True):
data = search_ebay(
query,
max_price=target_price,
condition=condition,
buy_now=buy_now,
sort="price_asc",
)
deals = []
for listing in data.get("results", []):
price = parse_price(listing.get("metadata", {}).get("price"))
if price is not None and price <= target_price:
deals.append({
"title": listing["title"],
"price": price,
"link": listing["link"],
"condition": listing["metadata"].get("condition"),
})
return deals
A couple of things worth calling out about the response shape. Each listing lives under results[].metadata, and the fields you’ll mostly care about are price, was_price (only present when eBay is showing a markdown), condition, seller, seller_feedback, and attributes — a grab-bag array that covers things like shipping cost, delivery estimate, and “or Best Offer” badges. There’s also a top-level total field, but it’s not reliable right now — it currently reports 0 regardless of how many listings actually matched, so don’t build any logic around it. Use len(results) (or len(data["results"]) in Python) if you need an actual count.
Wiring it into an alert loop
The tracker function is the whole point — everything else is just “run it periodically and do something when find_deals returns something.” A cron job or a simple sleep loop both work fine, since this isn’t latency-sensitive:
import time
WATCHLIST = [
{"query": "rtx+4070", "target_price": 450, "condition": "used"},
{"query": "sony+a7iii", "target_price": 900, "condition": "seller_refurbished"},
]
def check_watchlist():
for item in WATCHLIST:
deals = find_deals(item["query"], item["target_price"], item["condition"])
for deal in deals:
notify(deal, item["query"])
def notify(deal, query):
# Swap this for a Slack webhook, an email, a push notification --
# whatever gets your attention. Printing is enough to prove it works.
print(f"[DEAL] {query}: {deal['title']} — ${deal['price']:.2f}")
print(f" {deal['link']}")
if __name__ == "__main__":
while True:
check_watchlist()
time.sleep(60 * 30) # check every 30 minutes
Swap notify() for whatever actually gets your attention — a Slack webhook, an SMS via Twilio, a push notification. The tracking logic doesn’t change; only what happens when a deal is found does.
Sorting matters more than you’d think
One detail that’s easy to miss: since you’re sorting with sort=price_asc, the cheapest matching listing is always first. If you only care about “is there anything under my target price right now,” you technically only need to check results[0] — everything after it is more expensive. Checking the whole list is still worth it if you want to log multiple deals per run, but for a simple “just tell me the cheapest one” agent, the first result is your answer.
Where this goes from here
This same pattern — filtered search, price parsing, threshold comparison — works for anything with a price tag: a specific book edition, a discontinued board game, resale sneakers, a parts kit for a fifteen-year-old car. Swap the query and the condition filter, and the rest of the code doesn’t change.
If you want to go further, pair this with Serply’s Amazon product search to compare the same item across both marketplaces before deciding where to buy, or hook it up to an LLM agent as a tool call so it can decide on its own when a price is “good enough” instead of relying on a fixed threshold. Either way, the hard part — turning “used RTX 4070 under $450, Buy It Now” into a real, filterable API request — is already done for you. Grab an API key at serply.io and point it at api.serply.io to get started.