DEV Community

API Serpent
API Serpent

Posted on

Building with eBay Data in 2026 — 6 Endpoints, 10 Markets, and the Billing Trap Nobody Warns You About

 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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
# .env
SERPENT_API_KEY=your_key_here
Enter fullscreen mode Exit fullscreen mode
# 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()
Enter fullscreen mode Exit fullscreen mode

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"])
Enter fullscreen mode Exit fullscreen mode

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])
Enter fullscreen mode Exit fullscreen mode

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"]
)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"])
Enter fullscreen mode Exit fullscreen mode

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])
Enter fullscreen mode Exit fullscreen mode

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")
Enter fullscreen mode Exit fullscreen mode

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)