eBay Sold Listings API
The eBay sold listings API is GET /ebay/v1/sold: a keyword search over completed eBay sales on ebay.com and ebay.co.uk that answers with the final price, sale date, condition and shipping of every match, newest first, up to 2,000 in one call.
Returns sold listings whose title contains every word of query, in any order — or, with category and no query, every sale in that category. Results are always ordered newest sale first; there is no sort parameter.
Matching is on whole words, case- and accent-insensitive. Words are split on every non-alphanumeric character, so 64GB, s21 and 44mm are each one word, and iphone does not match iphones.
Paging. A page is 100 results. max_pages sets how many pages are returned, and all of them arrive in this one response: up to max_pages × 100 results, 2,000 at most. When count equals max_pages × 100 the response is full and more may exist: it then carries next_cursor, a string to send back unchanged as cursor on the same search for the next pages, from exactly where this response stopped. Repeat until next_cursor is null. A cursor walks as far as the data goes, is never stored or expired server-side, and is bound to the search that issued it (the same site, query, exclude, category, prices, dates, condition, attr and details; only max_pages may change) — on any other search it answers 400.
A whole category. query is required only when there is no category: a leaf category on its own returns every sale in it, newest first, and the cursor walks the rest.
Cost. 1 credit per page of results returned (count ÷ 100, rounded up), or 2 credits per page when details is set. A search that matches nothing answers 200 with count 0 and costs nothing. max_pages is the most the request can cost, not its price. The request is refused with 429 when the account's remaining credits do not cover max_pages (doubled with details).
Listing details. With details set, each result carries a details object identical to what GET /ebay/v1/item returns for its item_id, and results are limited to listings whose details are available and listings eBay has removed.
Request
https://api.trawl.dev/ebay/v1/sold Send the API key in the x-api-key header. The samples read it from the TRAWL_API_KEY environment variable.
Parameters
| Parameter | Type | Description |
|---|---|---|
query | string | Words that must all appear in the listing title, in any order. Matching is on whole words and is case- and accent-insensitive; the text is split into words on every non-alphanumeric character. At most 32 distinct words are accepted. Required unless category is sent: a request with neither answers 400; with a category and no query, every sale in the category is returned. |
site | EBAY_US | EBAY_GB | The marketplace to search. Each marketplace is searched separately; prices are in that marketplace's own currency and category ids are specific to it. The value is case-insensitive. |
exclude | string | Words that must not appear in the listing title, separated by commas or spaces. Each word is matched on its own, as a whole word. At most 32 distinct words are accepted, and none of them may also be in query. |
category | string | Restricts results to one eBay leaf category, given as its numeric id. The id must exist in the category tree of the chosen site and must be a leaf category; an unknown id, an id from another marketplace or a parent category answers 400. Look ids up with GET /ebay/v1/categories. On its own, with no query, it returns every sale in the category, newest first; cursor walks the whole of it. |
condition | (new | used | parts | other)[] | Restricts results to one or more conditions, comma-separated. A listing matches when its condition is any of the given values. Values are case-insensitive. |
attr | string[] | Filters on an item specific, written Key:Value and split at the first colon. The match is exact on the whole value and is case-, accent- and whitespace-insensitive. Repeat the parameter for several filters: different keys must all match, and repeating one key matches any of its values (attr=Grade:9&attr=Grade:10 is grade 9 or 10). At most 16 attr parameters are accepted in total. Only listings whose details are available have item specifics, so any attr filter limits results to those listings. The filter changes which listings match, not the shape of the response. |
min_price | number | Minimum sale price, inclusive, in the marketplace's own currency (USD on EBAY_US, GBP on EBAY_GB). No currency conversion is done. Must be less than or equal to max_price when both are set. |
max_price | number | Maximum sale price, inclusive, in the marketplace's own currency. |
date_from | date (YYYY-MM-DD) | Earliest sale date, inclusive, as YYYY-MM-DD on the marketplace's own calendar. Must be a real calendar date and must not be later than date_to when both are set. |
date_to | date (YYYY-MM-DD) | Latest sale date, inclusive, as YYYY-MM-DD on the marketplace's own calendar. Must be a real calendar date. |
max_pages | integer | How many pages of 100 results to return, all in this one response. It is also the most credits the request can cost: the charge is 1 credit per page of results actually returned, or 2 per page with details. A value outside 1 to 20, or one that is not a whole number, answers 400. When the parameter is omitted one page is returned, reported as max_pages 1. |
details | boolean | Set to 1 or true to include each result's full listing details: a details object per result, identical to the response of GET /ebay/v1/item for that item_id. This is also how to get the accepted amount of a Best Offer sale: the search's own sale_price is the listed price, and details.sale_price is the accepted amount where eBay publishes it (Collectibles categories). The search then costs 2 credits per page of results returned instead of 1, and results are limited to listings whose details are available and listings eBay has removed, so fewer results are returned than for the same search without it. 0, false or an empty value leaves details off; any other value answers 400. |
cursor | string | The next_cursor of a previous response, sent back unchanged to continue that search from exactly where it stopped. The search must be the same — site, query, exclude, category, prices, dates, condition, attr and details — or the request answers 400; max_pages may differ. It cannot be combined with page. A cursor is a position, not a session: nothing is stored server-side and it does not expire. A request with a cursor is priced like any other, and one that finds nothing more costs nothing. |
Call it
curl "https://api.trawl.dev/ebay/v1/sold?query=iphone+15+pro+256gb&condition=used&exclude=case,cracked&max_pages=5" \
-H "x-api-key: $TRAWL_API_KEY"from trawl_api import Trawl
client = Trawl() # reads TRAWL_API_KEY
sold = client.ebay.sold(
"iphone 15 pro 256gb",
condition="used",
exclude=["case", "cracked"],
max_pages=5,
)import { Trawl } from 'trawl-api'
const client = new Trawl() // reads TRAWL_API_KEY
const sold = await client.ebay.sold('iphone 15 pro 256gb', {
condition: 'used',
exclude: ['case', 'cracked'],
max_pages: 5,
})Response
The search ran. results holds the matching sales, newest first, and is empty when nothing matched.
Fields
| Field | Type | Description |
|---|---|---|
site | EBAY_US | EBAY_GB | The marketplace that was searched. |
currency | string | The ISO 4217 code of the marketplace's currency, in which min_price, max_price and each result's prices are expressed. USD on EBAY_US, GBP on EBAY_GB. |
query | string[] | The words of query as they were searched, lowercased, accent-folded and de-duplicated. |
exclude | string[] | The words of exclude as they were applied. Present only when exclude was sent. |
filters | object | The filters that were applied, echoed so a caller can confirm each parameter was understood. A filter that was not sent has no key; with no filters the object is empty. |
filters.category | string | The category id that was filtered on. |
filters.category_name | string | The name of that category in the searched marketplace's category tree. |
filters.min_price | number | The minimum sale price that was applied. |
filters.max_price | number | The maximum sale price that was applied. |
filters.date_from | date (YYYY-MM-DD) | The earliest sale date that was applied. |
filters.date_to | date (YYYY-MM-DD) | The latest sale date that was applied. |
filters.condition | (new | used | parts | other)[] | The condition values that were applied. |
filters.attr | object | The item-specific filters that were applied, keyed by the normalized (lowercased, accent-folded) key. A key filtered on one value maps to a string, and a key filtered on several values maps to an array of strings. |
details | boolean | Present, and true, only when the request set details. |
page | integer | Present, in place of max_pages and next_cursor, only when the request used the original one-page page parameter. |
max_pages | integer | The max_pages value of the request, 1 when it was not sent. |
next_cursor | string | null | A cursor to continue this search with, as the cursor parameter, when the response is full (count equals max_pages × 100) and more may exist. null when the search reached its end. |
count | integer | The number of entries in results. When it equals the number of results requested (max_pages × 100), more matching sales may exist — next_cursor continues. |
took_ms | integer | Server-side time spent running the search, in milliseconds. |
results | object[] | The matching sales, newest first. Empty when nothing matched. |
results[].title | string | The listing title. |
results[].sale_price | number | The price the item sold for, in the marketplace's currency, excluding shipping. For a sale that closed on an accepted Best Offer this is the listed price, because eBay's sold results do not show the accepted amount. To get the accepted amount, pass details=1 and read details.sale_price where details.best_offer_accepted is true; eBay publishes it on Collectibles listings (trading cards, comics, coins and the like) and on no other category, where every source has only the listed price. On EBAY_GB it is the price a UK buyer saw and paid, which for a private seller includes eBay's Buyer Protection fee. |
results[].shipping_price | number | null | The shipping cost the buyer paid, in the marketplace's currency. 0 for free shipping, null when it is not known. |
results[].currency | string | null | The currency symbol of the prices, as eBay prints it, for example $ or £. The ISO code is in the response's top-level currency. |
results[].condition | new | used | parts | other | The normalized condition. It takes the same values as the condition filter, so it can be passed back as one. |
results[].condition_raw | string | null | eBay's own wording for the condition, verbatim, for example Pre-Owned or New (Other). null when eBay showed no condition. Trading cards and coins read Pre-Owned here; their card condition, grader and grade are in the listing details. |
results[].date_sold | date-time | When the item sold, on the marketplace's own calendar (Pacific time for EBAY_US, UK time for EBAY_GB). The trailing Z is nominal; read the value as marketplace-local wall-clock time, not as a UTC instant. The time of day is included when eBay exposes it; sales recorded before September 2026 and sales from multi-quantity listings carry the day only, as midnight. |
results[].buying_format | string | null | How the item was sold, as eBay labels it, for example Buy It Now, Auction or Best Offer. |
results[].bids | integer | null | The number of bids on an auction. null for fixed-price listings. May be absent on older sales. |
results[].best_offer_available | boolean | null | Whether the listing accepted Best Offers. May be absent on older sales. |
results[].location | string | null | The item's location as eBay shows it in sold results, for example United States. May be absent on older sales. |
results[].item_id | string | The eBay item id of the listing. Pass it to GET /ebay/v1/item for the listing's details. |
results[].epid | string | null | The eBay product id (ePID) of the catalog product the listing is matched to. null when the listing has none. May be absent on older sales. |
results[].categoryId | string | The id of the leaf category the listing was sold in. |
results[].item_link | url | The URL of the listing on eBay. |
results[].image_url | url | null | The URL of the listing's primary image. |
results[].details | object | null | The listing's details. Present only when the request set details. It is the listing object that GET /ebay/v1/item returns for this item_id, or the removed-listing object when eBay has taken the listing down, or in rare cases null when the details could not be read at the time of the request. |
credits_charged | integer | Credits this response cost. 1 per page of results returned, 2 per page with details, and 0 when results is empty. |
Examples
{
"site": "EBAY_US",
"currency": "USD",
"query": [
"iphone",
"15",
"pro",
"256gb"
],
"exclude": [
"case",
"cracked"
],
"filters": {
"condition": [
"used"
]
},
"max_pages": 5,
"next_cursor": null,
"count": 3,
"took_ms": 41,
"results": [
{
"title": "Apple iPhone 15 Pro 256GB Natural Titanium Unlocked A2848",
"sale_price": 548,
"shipping_price": 0,
"currency": "$",
"condition": "used",
"condition_raw": "Pre-Owned",
"date_sold": "2026-09-28T14:32:00.000Z",
"buying_format": "Buy It Now",
"bids": null,
"best_offer_available": true,
"location": "United States",
"item_id": "256637082114",
"epid": "13062762640",
"categoryId": "9355",
"item_link": "https://www.ebay.com/itm/256637082114",
"image_url": "https://i.ebayimg.com/images/g/k3YAAOSwq1JlXvRt/s-l500.webp"
},
{
"title": "iPhone 15 Pro 256GB Blue Titanium - Unlocked - 89% Battery Health",
"sale_price": 512.5,
"shipping_price": 8.95,
"currency": "$",
"condition": "used",
"condition_raw": "Pre-Owned",
"date_sold": "2026-09-28T09:05:00.000Z",
"buying_format": "Auction",
"bids": 23,
"best_offer_available": false,
"location": "United States",
"item_id": "126702384915",
"epid": "13062762640",
"categoryId": "9355",
"item_link": "https://www.ebay.com/itm/126702384915",
"image_url": "https://i.ebayimg.com/images/g/9cMAAOSw2gxmQe7n/s-l500.webp"
},
{
"title": "Apple iPhone 15 Pro - 256GB - Black Titanium (Verizon) Good Condition",
"sale_price": 489.99,
"shipping_price": 0,
"currency": "$",
"condition": "used",
"condition_raw": "Good - Refurbished",
"date_sold": "2026-09-27T00:00:00.000Z",
"buying_format": "Buy It Now",
"bids": null,
"best_offer_available": false,
"location": "United States",
"item_id": "395218840762",
"epid": null,
"categoryId": "9355",
"item_link": "https://www.ebay.com/itm/395218840762",
"image_url": "https://i.ebayimg.com/images/g/Hq4AAOSwTmFl8w0b/s-l500.webp"
}
],
"credits_charged": 1
}{
"site": "EBAY_US",
"currency": "USD",
"query": [
"iphone",
"15",
"pro",
"256gb"
],
"filters": {
"condition": [
"used"
]
},
"details": true,
"max_pages": 1,
"count": 2,
"took_ms": 58,
"results": [
{
"title": "Apple iPhone 15 Pro 256GB Natural Titanium Unlocked A2848",
"sale_price": 548,
"shipping_price": 0,
"currency": "$",
"condition": "used",
"condition_raw": "Pre-Owned",
"date_sold": "2026-09-28T14:32:00.000Z",
"buying_format": "Buy It Now",
"bids": null,
"best_offer_available": true,
"location": "United States",
"item_id": "256637082114",
"epid": "13062762640",
"categoryId": "9355",
"item_link": "https://www.ebay.com/itm/256637082114",
"image_url": "https://i.ebayimg.com/images/g/k3YAAOSwq1JlXvRt/s-l500.webp",
"details": {
"site": "EBAY_US",
"item_id": "256637082114",
"title": "Apple iPhone 15 Pro 256GB Natural Titanium Unlocked A2848",
"condition": "used",
"condition_raw": "Pre-Owned",
"condition_description": "Light scratches on the frame, screen flawless. Battery health 91%.",
"grading": null,
"listing_state": "sold",
"sold_at": "2026-09-28T21:32:00.000Z",
"last_updated": "2026-09-26T18:04:11.000Z",
"sale_price": 548,
"currency": "US$",
"buying_format": "Buy It Now",
"bids": null,
"best_offer_available": true,
"best_offer_accepted": false,
"shipping_service": "USPS Ground Advantage",
"returns_text": "30 days returns. Buyer pays for return shipping.",
"seller_accepts_returns": true,
"location": "Austin, Texas",
"location_country": "United States",
"epid": "13062762640",
"categoryId": "9355",
"item_link": "https://www.ebay.com/itm/256637082114",
"images": [
"https://i.ebayimg.com/images/g/k3YAAOSwq1JlXvRt/s-l1600.webp",
"https://i.ebayimg.com/images/g/m~sAAOSwq1JlXvRu/s-l1600.webp"
],
"attributes": [
{
"key": "Brand",
"value": "Apple",
"values": [
"Apple"
]
},
{
"key": "Model",
"value": "Apple iPhone 15 Pro",
"values": [
"Apple iPhone 15 Pro"
]
},
{
"key": "Storage Capacity",
"value": "256 GB",
"values": [
"256 GB"
]
},
{
"key": "Network",
"value": "Unlocked",
"values": [
"Unlocked"
]
}
],
"description_url": "https://itm.ebaydesc.com/itmdesc/256637082114",
"description_text": "Fully unlocked iPhone 15 Pro, 256GB, Natural Titanium. Battery health 91%. Comes with the original box and an unused cable.",
"seller": {
"username": "phone-depot",
"feedback_percent": 99.6,
"feedback_count": 6864,
"items_sold": 12480
},
"feedback": [
{
"username": "b***y",
"rating": "positive",
"comment": "Exactly as described, fast shipping",
"age_text": "Past month"
}
],
"sales": [
{
"date_sold": "2026-09-28T00:00:00.000Z",
"sale_price": 548,
"shipping_price": 0,
"currency": "$",
"condition": "Pre-Owned"
}
]
}
},
{
"title": "iPhone 15 Pro 256GB Blue Titanium - Unlocked - 89% Battery Health",
"sale_price": 512.5,
"shipping_price": 8.95,
"currency": "$",
"condition": "used",
"condition_raw": "Pre-Owned",
"date_sold": "2026-09-28T09:05:00.000Z",
"buying_format": "Auction",
"bids": 23,
"best_offer_available": false,
"location": "United States",
"item_id": "126702384915",
"epid": "13062762640",
"categoryId": "9355",
"item_link": "https://www.ebay.com/itm/126702384915",
"image_url": "https://i.ebayimg.com/images/g/9cMAAOSw2gxmQe7n/s-l500.webp",
"details": {
"site": "EBAY_US",
"item_id": "126702384915",
"listing_state": "removed"
}
}
],
"credits_charged": 2
}{
"site": "EBAY_US",
"currency": "USD",
"query": [
"iphone",
"15",
"pro",
"256gb"
],
"filters": {
"min_price": 5000
},
"max_pages": 1,
"count": 0,
"took_ms": 12,
"results": [],
"credits_charged": 0
}Errors
Every error is a JSON object with one error string and is not charged. The statuses this endpoint answers with:
| Status | Meaning |
|---|---|
| 400 | A parameter failed validation. The message names the parameter and the rule it broke. Not charged. |
| 403 | The x-api-key header is missing, or the key is invalid, revoked or belongs to a closed account. Rate-limit headers are not sent on this response. |
| 429 | The request was refused and not charged. There are two cases, told apart by the Retry-After header. With Retry-After, the account's per-second rate was exceeded: wait that many seconds and retry. Without it, the account's monthly credits are spent, or too few remain to cover the most the request could cost: lower max_pages, wait for the time in X-RateLimit-Reset, or change plan. Retrying without one of those does not help. |
| 500 | An internal error. Not charged. |
| 503 | The service is temporarily unavailable, or the request took longer than 30 seconds. Not charged. Safe to retry with backoff. |