How do you scrape eBay listings via API without getting blocked?
Call ReefAPI's ebay search action with a query and a domain and read item ids, prices, conditions, shipping text, seller location and sold counts back as JSON. The shape to plan for: an eBay result set mixes auctions and fixed-price listings, and the auction rows genuinely do not carry the same fields.
This guide demonstrates the real eBay API engine with a captured response from . The example is only published because the engine passed the SEO snapshot gate.
Resale research, price benchmarking, inventory sourcing and marketplace analytics.
Call the live endpoint
- 1
Choose the domain, then the query
domain selects which of the 13 eBay marketplaces you search. It changes currency, language, inventory and the local price format all at once.
- 2
Give every optional field a default
Auction rows omit condition, location, shipping and sold_count as absent keys, not nulls. Three of our ten results were auctions.
- 3
Read price.raw on non-US domains
price.value is correct on com. On de we measured raw 'EUR 240,00' returning value 24000.0 - a decimal-separator defect we are fixing. Parse raw until then.
- 4
Use the search condition string for grading
Search returns 'Very Good - Refurbished' where detail returns just 'Refurbished'. The finer grade is the one that matters when you are pricing used inventory.
- 5
Enrich only the items you shortlist
items_batch fetches a list of ids in one call; item_detail adds brand, model, mpn, machine-readable availability and full-size images with dimensions.
Copy the request
These snippets use the captured request params for ebay/v1/search.
curl -X POST https://api.reefapi.com/ebay/v1/search \
-H "x-api-key: $REEF_KEY" \
-H "content-type: application/json" \
-d '{"query":"iphone 13 pro"}'import requests
r = requests.post(
"https://api.reefapi.com/ebay/v1/search",
headers={"x-api-key": REEF_KEY},
json={
"query": "iphone 13 pro"
},
)
print(r.json()["data"])const res = await fetch("https://api.reefapi.com/ebay/v1/search", {
method: "POST",
headers: {
"x-api-key": process.env.REEF_KEY,
"content-type": "application/json",
},
body: JSON.stringify({
"query": "iphone 13 pro"
}),
});
const { ok, data, meta, error } = await res.json();Ask your MCP-connected assistant: call reefapi.ebay.search with {"query":"iphone 13 pro"}.Captured output from ReefAPI
Captured on UTC. The response below is the committed snapshot, including the API envelope and metadata.
{
"method": "POST",
"url": "https://api.reefapi.com/ebay/v1/search",
"headers": {
"x-api-key": "$REEF_KEY",
"content-type": "application/json"
},
"body": {
"query": "iphone 13 pro"
}
}{
"ok": true,
"meta": {
"api": "ebay",
"endpoint": "search",
"mode": "live",
"latency_ms": 2338,
"record_count": 60,
"bytes": 2513278,
"cache_hit": false,
"version": "0.1",
"domain": "com",
"pagination": {
"page": 1,
"has_more": true
},
"charged_credits": 1
},
"data": {
"query": "iphone 13 pro",
"results": [
{
"item_id": "286910050302",
"title": "Apple iPhone 13 128GB 256GB 512GB Verizon AT&T T-Mobile Unlocked (Very Good)",
"price": {
"raw": "$258.49",
"value": 258.49,
"currency": "USD"
},
"condition": "Very Good - Refurbished",
"buying_format": "Buy It Now",
"shipping": "Shipping not specified",
"location": "United States",
"image": "https://i.ebayimg.com/images/g/fcQAAeSw7QJpcweM/s-l500.webp",
"url": "https://www.ebay.com/itm/286910050302",
"in_stock": true,
"catalog_rating": {
"average": 5,
"review_count": 0
}
},
{
"item_id": "286910053729",
"title": "Apple iPhone 13 128GB 256GB 512GB Verizon AT&T T-Mobile Unlocked (Good)",
"price": {
"raw": "$248.59",
"value": 248.59,
"currency": "USD"
},
"condition": "Pre-Owned",
"buying_format": "Buy It Now",
"shipping": "Shipping not specified",
"location": "United States",
"image": "https://i.ebayimg.com/images/g/ic8AAeSwVeRpcwzG/s-l500.webp",
"url": "https://www.ebay.com/itm/286910053729",
"sold_count": "15+ sold",
"in_stock": true,
"catalog_rating": {
"average": 5,
"review_count": 0
}
},
{
"item_id": "287062398385",
"title": "Apple iPhone 13 Pro A2483 Unlocked 256GB Graphite (Very Good)",
"price": {
"raw": "$428.99",
"value": 428.99,
"currency": "USD"
},
"condition": "Apple iPhone 13 Pro",
"buying_format": "Buy It Now",
"shipping": "Shipping not specified",
"location": "United States",
"image": "https://i.ebayimg.com/images/g/31IAAeSwLmtpYJUx/s-l500.webp",
"url": "https://www.ebay.com/itm/287062398385",
"in_stock": true,
"catalog_rating": {
"average": 4.5,
"review_count": 0
}
}
]
}
}Why this is hard manually
eBay result sets are not homogeneous, and code written against the first row will crash on the sixth. In our ten-result 'nintendo switch oled' search, three rows were auctions (buying_format '17 bids', '10 bids', '8 bids') and those three carried no condition, no location, no shipping and no sold_count - the keys were absent from the object entirely, not present with a null. A schema that requires condition will reject nearly a third of a normal eBay page.
Even among the fixed-price rows the optional keys move around: best_offer appeared on two of ten, catalog_rating on two, sold_count on four. There is no stable superset. This is not eBay being inconsistent for its own amusement - a seven-day auction with no completed sales genuinely has no sold count - but it does mean every field access needs a default.
The third thing is that eBay is thirteen marketplaces with thirteen sets of local conventions, and the most dangerous of those is how a decimal point is written.
Why ReefAPI solves it
Prices come as an object with three views: raw ('$184.99'), value (184.99) and currency ('USD'). One caveat we would rather state than have you discover: on the German site the numeric value is currently wrong. raw 'EUR 240,00' came back as value 24000.0, and 'EUR 169,69' as 16969.0 - the comma decimal separator is being stripped rather than parsed. It is a defect on our side, it affects non-US domains, and we are fixing it. Until then, on any domain other than com, parse raw and treat value as advisory. The US domain returns value correctly.
condition is coarser on the product page than in the result list, which is the opposite of the usual direction. Search returned 'Very Good - Refurbished' for item 267673902801; item_detail on the same id returned 'Refurbished'. Both are true, and the search string is the more useful one for grading resale inventory. item_detail adds what search cannot: brand, model, mpn, availability as a machine value ('InStock'), and full-resolution images with dimensions and, on many listings, alt-text descriptions.
shipping is a display string, not a number, and it carries three quite different meanings: '+$1.00 delivery' is a charge, 'Free International Shipping' is free, and 'Shipping not specified' means the seller has not published one. Treating the third as free is the mistake that ruins landed-cost comparisons.
sold_count is a string with two formats and it is the most useful demand signal eBay exposes: '568 sold' is exact, '69+ sold' is a floor. Parse the plus sign rather than the number alone - a '69+' and a '69' are not the same claim. location tells you where the item ships from ('Japan', 'United States'), which for used-console arbitrage is often more decision-relevant than the price.
mpn can come back as the literal string 'Does Not Apply'. That is the seller's own entry in eBay's form, not a null we introduced, and it is what distinguishes 'this seller left it blank' from 'this seller says there is no part number'. We pass it through.
Eight actions share the engine: search, sold_search, item_detail, items_batch, seller_items, seller_profile, deals and categories. sold_search - the completed-listings research surface - is returning DISABLED at the time of writing with a retry message; the others are live. items_batch takes a list of item ids in one request, and seller_items plus seller_profile together give you a competitor's whole active inventory. Search answered in about 3.4 seconds, detail in 1.2.
Questions developers ask
Do I need an eBay developer account?
No. You send a ReefAPI key. There is no eBay app id, no OAuth token and no production-key approval process.
Why do some results have no condition field at all?
Because they are auctions. Three of our ten results had buying_format '17 bids', '10 bids' and '8 bids', and those rows omitted condition, location, shipping and sold_count entirely. The keys are absent rather than null, so use a default rather than a null check.
The German prices look a hundred times too high. Is that real?
No, it is our bug and we are fixing it. On non-US domains the comma decimal separator is being stripped: raw 'EUR 240,00' yields value 24000.0. price.raw is correct. Parse raw on any domain other than com.
What does 'Shipping not specified' mean?
That the seller has not published a shipping cost, which is different from free shipping. The shipping field is a display string with three distinct meanings - a charge ('+$1.00 delivery'), free ('Free International Shipping'), or unstated. Do not collapse the third into zero.
How reliable is sold_count?
It is eBay's own figure and comes in two forms: exact ('568 sold') and a floor ('69+ sold'). Keep the plus sign. It appeared on four of our ten results - auctions and new listings do not have one.
Can I research completed and sold prices?
sold_search exists for exactly that, but it is returning DISABLED at the time of writing with a retry message. The active-listing surfaces - search, seller_items, item_detail - are live.
Why is mpn 'Does Not Apply'?
Because that is what the seller typed into eBay's form. It is a real value meaning the seller asserts there is no manufacturer part number, which is different from leaving the field empty. We do not convert it to null.