CompSniper
Python tutorial

How to get eBay sold listings with Python in 2026

Build a working Python integration that returns real completed sales, sold prices, dates, conditions, sellers, and a cleaned median—then add filters, pagination, and safe retries.

Published August 30, 202610 minute readTested with Python 3.12
01

Install requests and protect your API key

Create a free CompSniper account, copy the key from the dashboard, and keep it in an environment variable. API keys are server-side credentials; never put one in browser JavaScript or commit it to Git.

Terminal
python -m pip install requests
export COMPSNIPER_API_KEY="cs_live_REPLACEWITHYOURKEY"
02

Request completed eBay sales

Call GET /v1/scrape with a keyword. This example asks for one page of used Shure SM7B sales and prints the summary plus five evidence rows.

Python
import os
import requests

response = requests.get(
    "https://api.compsniper.com/v1/scrape",
    headers={
        "Authorization": f"Bearer {os.environ['COMPSNIPER_API_KEY']}"
    },
    params={
        "keyword": "shure sm7b",
        "count": 240,
        "itemCondition": "used",
    },
    timeout=75,
)
response.raise_for_status()
data = response.json()

print("Listings:", data["totalItems"])
print("Median:", data["summary"]["median"], data["summary"]["currency"])
print("Realistic range:", data["summary"]["p25"], "-", data["summary"]["p75"])

for item in data["items"][:5]:
    print(item["title"], item["soldPrice"], item["soldCurrency"], item["endedAt"])
Editorial illustration of completed product listings flowing through a developer laptop into a price distribution chart and resale analytics.
Completed marketplace listings flow through a developer integration into cleaned price intelligence.
03

Read the sold rows and price summary

Every response includes pagination metadata, a sold-listing array, and deterministic price intelligence over the returned sample. Prices are decimal strings on listing rows and numbers in the summary.

Live sample · JSON
{
  "keyword": "shure sm7b",
  "page": 1,
  "totalItems": 207,
  "totalResults": "475",
  "rawMedian": 200,
  "rawSampleCount": 240,
  "summary": {
    "count": 207,
    "currency": "USD",
    "median": 215,
    "mean": 227.36,
    "min": 90,
    "max": 550,
    "p25": 175.71,
    "p75": 260,
    "avgShipping": 9
  },
  "items": [
    {
      "title": "Shure SM7B Cardioid Dynamic Vocal Microphone - Complete in Box Excellent",
      "soldPrice": "199.99",
      "soldCurrency": "USD",
      "endedAt": "2026-08-29"
    }
  ]
}

What relevance cleanup changed in this sample

Collected August 30, 2026 from ebay.com. The raw page contained 240 priced rows with a $200 median. After accessory, part, and wrong-model cleanup, 207 rows remained with a $215 median and a $175.71–$260 interquartile range. This is one observed sample, not a promise that every keyword changes by the same amount.

04

Narrow the result set before pricing

Use explicit API filters for condition, price, marketplace, date, category, and listing format. Use eBay minus-sign exclusions in the keyword when a predictable accessory keeps appearing.

Python parameters
params = {
    "keyword": "iphone 15 pro -case -screen -charger",
    "ebaySite": "ebay.com",
    "itemCondition": "used",
    "minPrice": 250,
    "maxPrice": 1200,
    "count": 100,
}

Eight marketplaces

Change ebaySite without changing your parser.

Raw evidence available

Set relevance=false when you explicitly need every upstream match.

05

Handle rate limits without creating a retry storm

A temporary per-minute limit and an exhausted monthly quota both use HTTP 429, but their response codes require opposite behavior. Retry rate_limited after Retry-After. Never loop on quota_exceeded.

Python · bounded retries
import random
import time
import requests

def search_with_safe_retries(url, headers, params):
    for attempt in range(5):
        response = requests.get(url, headers=headers, params=params, timeout=75)
        body = response.json()

        if response.ok:
            return body

        if response.status_code == 429 and body.get("code") == "quota_exceeded":
            raise RuntimeError(
                f"Quota exhausted. Reset: {body.get('reset_at')}. "
                f"Upgrade: {body.get('upgrade_url')}"
            )  # Never retry monthly quota exhaustion.

        temporary = (
            response.status_code == 429 and body.get("code") == "rate_limited"
        ) or response.status_code in (500, 502, 503)

        if temporary and attempt < 4:
            delay = float(
                response.headers.get("Retry-After")
                or body.get("retry_after")
                or min(2 ** attempt, 30)
            )
            time.sleep(delay + random.uniform(0, 0.5))
            continue

        raise RuntimeError(body.get("error", f"HTTP {response.status_code}"))

    raise RuntimeError("Request failed after five bounded attempts")

Successful responses expose X-Usage-Remaining. Slow down or ask the user to upgrade before an automated job reaches zero.

06

Paginate with an explicit safety bound

Increment page until hasNextPage is false. Each successful page uses one request, so production jobs should always define a maximum number of pages.

Python · three pages maximum
items = []

for page in range(1, 4):  # Deliberately bounded: each page uses one request.
    response = requests.get(
        "https://api.compsniper.com/v1/scrape",
        headers={"Authorization": f"Bearer {os.environ['COMPSNIPER_API_KEY']}"},
        params={"keyword": "sony wh-1000xm5", "page": page, "count": 240},
        timeout=75,
    )
    response.raise_for_status()
    data = response.json()
    items.extend(data["items"])

    if not data["hasNextPage"]:
        break

print("Collected rows:", len(items))
07

Move from example to production

Frequently asked questions

Python and eBay sold data

How many sold listings can one Python request return?

Up to 240 sold listings per page. One successfully returned page uses one request from the account quota.

Can I retrieve sold prices from eBay marketplaces outside the US?

Yes. The same response format supports the US, UK, Germany, France, Italy, Spain, Canada, and Australia through the ebaySite parameter.

Should Python retry every HTTP 429 response?

No. Retry rate_limited only after Retry-After. Stop immediately on quota_exceeded and wait for reset_at or direct the user to upgrade_url.

Can I receive raw eBay matches without relevance cleanup?

Yes. Set relevance=false. The default relevance cleanup removes likely accessories, parts, and wrong-model matches before the price summary is calculated.

Make your first sold-price request

100 free requests every month. No credit card required.