I want to start this post with the single most important thing to understand about eBay data APIs before you write a line of code.
A search request and a product record are not the same thing.
This sounds obvious, but it's responsible for more eBay API budget surprises than almost any other mistake. I'll explain the distinction, then show you how to use each of the six operations Serpent API's eBay endpoint exposes — with real code for each one.
The Data Model
eBay's public data has a natural hierarchy:
Search query
└── Listing page (contains N listing cards)
└── Listing card (title, price, condition, seller, item link)
└── Item detail (images, specifics, availability, description)
└── Seller profile (identity, feedback score, positive rate)
└── Seller listings (inventory, page by page)
└── Seller feedback (buyer comments, ratings)
└── Product reviews (review text, rating, author)
Each layer is a separate API call. Each has its own rate. The mistake teams make is assuming that a "search" returns all of this. It doesn't — search returns the top level only. Enrichment requires additional calls per item.
This isn't a flaw — it's correct data architecture. You shouldn't pay for description data on items you're not interested in. But it means your cost model needs to account for every layer your application touches.
Setup
pip install requests python-dotenv
# .env
SERPENT_API_KEY=your_key_here
# ebay_client.py
import requests
import os
API_KEY = os.getenv("SERPENT_API_KEY")
BASE = "https://apiserpent.com/api/ebay"
HEADERS = {"X-API-Key": API_KEY}
def get(endpoint, params):
r = requests.get(f"{BASE}{endpoint}", params=params, headers=HEADERS, timeout=30)
r.raise_for_status()
return r.json()
Operation 1 — Search Listings
GET /api/ebay/search returns a page of listings for a keyword on a specified marketplace.
def search_listings(query, marketplace="EBAY_US", locale="en-US", page=1):
"""
Returns listing cards: title, price, condition,
shipping, seller identity, item link.
10 supported marketplace/locale pairs:
EBAY_US/en-US EBAY_GB/en-GB EBAY_DE/de-DE
EBAY_AU/en-AU EBAY_IT/it-IT EBAY_FR/fr-FR
EBAY_CA/en-CA EBAY_ES/es-ES EBAY_NL/nl-NL
EBAY_PL/pl-PL
Rate: $0.40/1K calls at Default tier
"""
return get("/search", {
"q": query,
"marketplace_id": marketplace,
"locale": locale,
"page": page
})
# Example
results = search_listings("vintage camera", "EBAY_DE", "de-DE")
for listing in results.get("listings", []):
print(listing["title"], listing["price"], listing["currency"])
What the response contains:
- title — listing title as displayed
- price / currency — displayed price in the marketplace's currency
- condition — New, Used, Refurbished, For parts, etc.
- shipping — shipping cost and type if displayed
- seller — seller username
- item_url — full item URL (use as item ID for subsequent calls)
- item_id — eBay item ID
The page trap: a search returns one page. If eBay shows 48 listings on page 1, you get 48 rows. If 12 are sponsored/promoted and you filter them, you have 36. Cost is per page request, not per usable listing. Always measure yield on your actual keywords before budgeting at scale.
Operation 2 — Item Details
GET /api/ebay/item returns structured data for a specific item by ID or URL.
def get_item(item_id, include_description=False):
"""
Returns price, images, item specifics, availability, seller.
Add include_description=1 to get the full seller HTML description.
Description calls: $1.50/1K (3.75x the standard rate).
If description unavailable but core item exists, standard rate applies.
Use include_description only when your pipeline needs it —
most price monitoring workflows don't.
"""
params = {"item_id": item_id}
if include_description:
params["include_description"] = 1
return get("/item", params)
# Price check without description (standard rate)
item = get_item("v1|123456789012|0")
print(item["price"], item["currency"], item["condition"])
# With full description (description rate)
item_full = get_item("v1|123456789012|0", include_description=True)
print(item_full.get("description", "")[:200])
When to use include_description:
- Product detail pages that show the seller's own copy
- Condition verification that requires reading the description text
- Content analysis pipelines
When NOT to use it:
- Price monitoring (price is in the core item)
- Stock tracking (availability is in the core item)
- Search enrichment loops (you'd pay 3.75× for data you don't use)
- Operation 3 — Seller Profile
GET /api/ebay/seller returns identity and reputation data for a seller username.
def get_seller_profile(seller_username):
"""
Returns: seller identity, feedback score,
positive feedback rate, profile link.
Useful for: trust scoring, seller monitoring,
marketplace research, fraud detection signals.
Rate: $0.40/1K calls
"""
return get("/seller", {"seller": seller_username})
profile = get_seller_profile("vintage_camera_shop")
print(
profile["feedback_score"],
profile["positive_feedback_rate"],
profile["member_since"]
)
Operation 4 — Seller Inventory
GET /api/ebay/seller/items reads a seller's public listings, one page at a time.
def get_seller_inventory(seller_username, page=1):
"""
Returns structured item cards from a seller's active listings.
Paginate to collect the full inventory.
For large inventories (100+ pages), use a background job
when listed in the API catalog — only served pages are charged.
Rate: $0.40/1K calls or served pages
"""
return get("/seller/items", {
"seller": seller_username,
"page": page
})
def collect_all_seller_items(seller_username, max_pages=10):
"""Collect a seller's full inventory page by page."""
items = []
for page in range(1, max_pages + 1):
data = get_seller_inventory(seller_username, page)
page_items = data.get("items", [])
if not page_items:
break
items.extend(page_items)
print(f"Page {page}: {len(page_items)} items")
return items
Direct calls vs. background jobs: For a seller with 400 listings across 16 pages, a direct loop makes 16 synchronous calls. A background job submits once, handles retries internally, and charges only for pages that complete. If pages 15–16 don't complete, you're not charged for them. For large inventories, background jobs are both cheaper and more reliable.
Operation 5 — Product Reviews
GET /api/ebay/product/reviews returns buyer reviews for an eBay catalog product ID.
def get_product_reviews(product_id):
"""
Returns: review text, star rating, author username, date.
Note: requires an eBay catalog product_id (e-pid),
not a listing item_id. These are different identifiers.
Rate: $0.40/1K calls
"""
return get("/product/reviews", {"product_id": product_id})
reviews = get_product_reviews("235190997")
for review in reviews.get("reviews", []):
print(review["rating"], review["title"], review["date"])
The product_id vs item_id distinction: eBay has two identifier systems. An item_id identifies a specific listing from a specific seller. A catalog product ID (often called e-pid or epid) identifies a standardized product in eBay's catalog — the same physical product sold by many sellers. Reviews are attached to the catalog product, not to individual listings. If your workflow starts from search results, you'll need to extract the product ID from the item detail before calling reviews.
Operation 6 — Seller Feedback
GET /api/ebay/seller/feedback returns feedback entries left by buyers.
def get_seller_feedback(seller_username, page=1):
"""
Returns: feedback entries with buyer username,
positive/negative/neutral rating, comment text, date.
Useful for: sentiment analysis, seller trust scoring,
fraud signals, marketplace research.
Rate: $0.40/1K calls
"""
return get("/seller/feedback", {
"seller": seller_username,
"page": page
})
feedback = get_seller_feedback("vintage_camera_shop")
for entry in feedback.get("feedback", []):
print(entry["rating"], entry["comment"][:80])
A Complete Price Monitoring Pipeline
Here's how the operations compose into a real workflow:
import time
from datetime import datetime
def run_price_monitor(keywords, marketplaces, output_file="prices.jsonl"):
"""
For each keyword × marketplace pair:
1. Search listings (discovery)
2. Get item detail for top N results (enrichment)
3. Log price, condition, seller, timestamp
Cost at Default tier ($0.40/1K):
- 1 search call + N item calls per keyword/market pair
- For 10 keywords × 3 markets × top-5 items:
30 search calls + 150 item calls = 180 calls total
180 / 1000 × $0.40 = $0.072 per run
"""
import json
results = []
for keyword in keywords:
for marketplace_id, locale in marketplaces:
# Layer 1: Search
search_data = search_listings(keyword, marketplace_id, locale)
listings = search_data.get("listings", [])[:5] # top 5 only
for listing in listings:
item_id = listing.get("item_id")
if not item_id:
continue
# Layer 2: Item detail (no description — not needed for price monitoring)
try:
item = get_item(item_id)
record = {
"timestamp": datetime.utcnow().isoformat(),
"keyword": keyword,
"marketplace": marketplace_id,
"item_id": item_id,
"title": listing["title"],
"search_price": listing.get("price"),
"item_price": item.get("price"),
"currency": item.get("currency"),
"condition": item.get("condition"),
"seller": listing.get("seller"),
"availability": item.get("availability"),
}
results.append(record)
# Write incrementally so partial runs aren't lost
with open(output_file, "a") as f:
f.write(json.dumps(record) + "\n")
except Exception as e:
print(f"Item {item_id} failed: {e}")
time.sleep(0.1) # gentle rate limiting
return results
# Run it
KEYWORDS = ["vintage camera", "film camera", "polaroid camera"]
MARKETS = [
("EBAY_US", "en-US"),
("EBAY_GB", "en-GB"),
("EBAY_DE", "de-DE"),
]
data = run_price_monitor(KEYWORDS, MARKETS)
print(f"Collected {len(data)} price records")
Cost Reality Check
At the Default tier ($0.40/1K calls):

For price monitoring at meaningful scale — 100 keywords × 10 markets × 20 items daily — that's roughly 21,000 calls/day, $8.40/day at Default tier, $0.84/day at Scale tier ($0.03/1K after the $500 one-time deposit).
Start free: apiserpent.com/ebay-api — 10 calls, no card, covers the search + item workflow for initial testing.
Full endpoint reference: apiserpent.com/docs#ebay-api
eBay is a trademark of eBay Inc. Serpent API is not affiliated with or endorsed by eBay Inc.
Top comments (0)