SidelineSwap API & Scraper
SidelineSwap is the largest US peer-to-peer marketplace for used sporting goods, and this API reads it as JSON with no account and no key.
🤖 Using an AI assistant? Copy this link into ChatGPT / Claude / Cursor — it reads every endpoint and parameter instantly and tells you if this API fits your use case.
On 2026-10-08 it carried 452,454 live listings across 36 sports - golf 107,432, hockey 87,319, baseball 73,072, apparel 56,226, footwear 42,512, lacrosse 21,776, skiing 21,548, softball 21,091, football 11,951, racket sports 11,051, snowboarding 9,575, soccer 8,207, memorabilia 7,205, and eighteen more - plus 2,086,777 SOLD listings. `search` takes free text and SidelineSwap's own filters: sport or category, brand, model, condition, handedness, pro-stock against retail, price bounds, seller type, seller region, US ZIP proximity, price drops, free shipping, the site's own editorial picks, and the category-specific attributes it publishes per category. The gear-specific values come back as their OWN fields rather than as text inside the title: a hockey stick row carries Flex, Pattern (the curve), Hand, Stick Length, Pro Stock and Age Group separately, and `filters` lists all of them with the ids to filter on - 88 flex values, 64 curve patterns, 48 stick lengths, 65 stick brands. Which attributes exist depends on the category, which is why `filters` is a per-category lookup: skiing publishes Boot Size, soccer publishes Size, golf publishes Hand and Gender, baseball publishes Colour and Age Group. `detail` returns a listing in full - the seller's own description, every attribute, condition tier, asking price against retail price, parcel dimensions and weight, shipping and delivery estimate, every photo, offer and auto-price-drop settings, and the seller with their badges and feedback totals. `comps` is the one that is hard to get elsewhere: it reads the sold side and returns min, p25, median, p75, max and mean for a model or category next to the same statistics for the live side, so an asking price can be read against what gear like it actually sold for. `models` returns the named products listings are grouped under with their available and sold counts, `categories` the 36-sport tree, and `seller` a public profile with feedback and live listings.
What a SidelineSwap field actually contains
Fields that read differently from how they look, and the limits worth knowing before you build on them. Every row was measured against live SidelineSwap responses on 2026-10-07 and 2026-10-08.
| Field | What it holds |
|---|---|
| total | SidelineSwap's own count for the query, and it is the real one. The site's own category pages print 50,000 with a truncation flag for anything large - three measurably different hockey queries all read "50000" on the web page - while this API answered 87,319 for the same sport and 452,454 unfiltered. total is the uncapped figure and it tracks every filter: against an unfiltered 19,268 hockey sticks in the same run, Used 6,274, New 12,994, left-handed 12,149, right-handed 7,137, pro stock 11,719, Bauer 4,781, CCM 6,896. |
| price_usd with state | One field, two meanings, and `state` is what tells them apart. On a live listing it is the asking price; on a sold listing it is the price the item ACTUALLY transacted at. That is why `state` is on every row and why `comps` exists. Checked against the price the item page itself publishes on 48 listings across six sports and both states: 48/48 exact. |
| state and the sold leak | `available` is the default, `sold` reads the sold side, `all` reads both, and the three are exactly additive: on hockey sticks available 19,245 + sold 149,405 = 168,650 for all. One caveat we publish rather than hide - the live feed keeps a just-sold listing for a short while, measured at 1 and 2 rows per 200 under the default order and 0 per 200 under `newest`. Each row's own `state` says which it is, and `sold_rows_in_available` counts them on every response. |
| attributes and attributes_flat | The gear columns, as the source publishes them per category, not parsed out of the title. `attributes` is the list with ids; `attributes_flat` is a `{slug: value}` map for quick reading - `{"hand": "Right", "hockey-stick-flex-individual": "70 Flex", "stick-pattern": ["P28", "Toe"], "age-group": "Senior", "condition": "New"}`. A group with two values comes back as a list, because a curve can be both a pattern family and a lie point. Filled on 12/12 detail calls across four sports. |
| detail (the parameter) and filters | Attribute ids are scoped to the exact category that carries them, and this is the one thing worth reading twice. A hockey-stick attribute such as Hand or Flex filters under Hockey > Sticks (110023) but NOT under Hockey (2000): the parent category does not carry those attributes, and asking anyway returns the unfiltered set. We refuse that combination with a message naming the id instead of returning an unfiltered result as a filtered one. Call `filters` on the category you are actually searching and use the ids it gives you. |
| condition | Two tiers only, the source's own: New and Used. There is no multi-grade ladder here - the wear detail lives in the seller's description and the photos. The two ids are global, verified identical across eight sports, so `condition` works the same everywhere. Measured on hockey sticks: Used 6,274 of 19,268, New 12,994. |
| age group | Reached through `detail` + `filters`, not as its own parameter, because the values differ per sport: hockey has Youth / Junior / Intermediate / Senior, baseball has Tee Ball (4-8) / Kid Pitch (9-13) / High School & College / Adult / Unknown, lacrosse has Adult / Youth. A single age-group enum would be wrong in most sports. |
| model | A model id groups every listing of one named product - `266943` is the Bauer Proto2 Hockey Stick, with 253 live and 912 sold. It is the sharpest input for `comps`. It cannot be combined with brand, condition, hand, pro_stock or detail: the source ignores all of them when a model is set (five filters, five identical responses), so we reject that combination rather than report a filter that did nothing. Price bounds, state and sort do still work with it. |
| comps statistics | The statistics describe the sample that was read, not the whole sold history, and both numbers are always returned side by side. Bauer Proto2 on 2026-10-08: total_sold 912, sold_sample_size 100, sold median $221.10, live median $245.00 over 253 available, median_sold_vs_available_pct 90.2. Sample up to 200 rows per call, newest first. |
| sold_via_offer | On a sold row, whether the sale went through an accepted offer rather than the listed price. Measured 50/50 filled on the sold surface with 23 of 50 true; it is null on live listings, where there is nothing to report yet. |
| retail_price_usd | The source's own retail reference, present on 23 of 100 live rows - not a majority, so treat it as a bonus rather than a baseline. `discount_vs_retail_pct` is computed from it and is null whenever it is missing. `list_price_usd` is filled 100/100 and tracks `price_usd`. |
| seller | The marketplace profile as the page shows it: username, profile link, badges and emblems (Pro Seller, Elite Seller, Quick Shipper, Verified Athlete, charity), feedback score with the positive/neutral/negative split, sales count, followers, the region and country they ship from, and their live/sold/draft counts. No name, street address, phone or e-mail - the source does not publish any of that on this surface and we do not go looking for it. |
| shipping | `feed_shipping_cost_usd` is the figure SidelineSwap publishes in its product feed and is filled on 12/12 detail calls. A real per-listing flat rate (`flat_rate_us_usd`) exists on only 1 of 48 listings, so do not build on it. `parcel` gives measured length, width, height and weight on 12/12, which is usually the more useful thing. |
| gtin, mpn | Sparse, not absent: 8 of 40 and 2 of 40 on new-condition golf and baseball listings, 0 of 12 on a mixed used sample. Most used gear simply has no barcode attached to it. The model-level gtin is effectively empty (1 of 750 models across three categories) and is returned only for completeness. |
| paging | total_pages is honest and the source does not repeat its last page - past the end it returns an empty page with has_more false (hockey-stick pages 963, 964 and 1000 all came back empty) rather than silently serving the last one again. page_size runs 1-200; 20 rows measured 29.6 KB and 1.4 s, 200 rows 285 KB and 2.3 s. Relevance order is reranked per request and is not reproducible between two identical calls - use `newest` or a price order when you need a stable page. |
| sort | Six orders, the site's own: relevance, newest, last_updated, trending, price_desc, price_asc. An unrecognised value is ignored upstream and silently returns relevance order over everything, so this is a closed list and anything else is refused with the list. |
Prices are USD. Sellers ship from the US (all regions) and Canada; `seller_location` filters on the source's own six regions. Measured across ~400 live calls on 2026-10-07 and 2026-10-08: no rate limiting and no blocked response at any point.
Real request and response JSON
Captured from the indexed primary action, search, on .
{
"method": "POST",
"url": "https://api.reefapi.com/sidelineswap/v1/search",
"headers": {
"x-api-key": "$REEF_KEY",
"content-type": "application/json"
},
"body": {
"category": "110023",
"detail": [
76
],
"condition": [
"used"
],
"min_price": 50,
"sort": "price_asc",
"page_size": 20
}
}{
"ok": true,
"meta": {
"api": "sidelineswap",
"endpoint": "search",
"mode": "live",
"latency_ms": 2481.2,
"record_count": 20,
"bytes": 95815,
"cache_hit": false,
"upstream_requests": 2,
"charged_credits": 2,
"version": "1.0.0",
"request_id": "be029c38964048a0",
"queue_ms": 4.1,
"fetched_at": "2026-10-08T11:33:21.312Z"
},
"data": {
"query": null,
"total": 2399,
"page": 1,
"page_size": 20,
"last_page": 120,
"has_more": true,
"state": "available",
"sort": "price_asc",
"filters_applied": {
"category_id": 110023,
"detail_ids": [
76,
17
],
"condition": [
"used"
],
"min_price_usd": 50,
"state": "available",
"sort": "price_asc"
},
"sold_rows_in_available": 0,
"items": [
{
"id": 11438512,
"name": "Intermediate Pro Blackout Right Handed Hockey Stick P92 70 Flex (Used)",
"state": "available",
"price_usd": 50,
"list_price_usd": 50,
"retail_price_usd": null,
"discount_vs_retail_pct": null,
"condition": {
"id": 17,
"slug": "used",
"name": "Used"
},
"sport": "hockey",
"category_slug": "sticks",
"url": "https://sidelineswap.com/gear/hockey/sticks/11438512-intermediate-pro-blackout-right-handed-hockey-stick-p92-70-flex-used",
"image": {
"id": 89237915,
"url": "https://images.sidelineswap.com/production/089/237/915/9c61dfa348ac4c7e_original.jpeg",
"thumb_url": "https://images.sidelineswap.com/production/089/237/915/9c61dfa348ac4c7e_original.jpeg"
},
"seller": {
"id": 401925,
"username": "Zackmese",
"url": "https://sidelineswap.com/Zackmese",
"emblems": [],
"badges": [],
"feedback_score": null,
"feedback_count": null,
"avatar_url": null
},
"favorites": 23,
"views": 457,
"label": null,
"sold_via_offer": null,
"listed_at": "2026-01-02T15:39:05.000-05:00",
"updated_at": "2026-10-01T04:52:17.000-04:00"
},
{
"id": 12713021,
"name": "Youth Bauer Vapor Hyperlite Hockey Stick Right Handed P28 30 Flex (Used)",
"state": "available",
"price_usd": 50,
"list_price_usd": 50,
"retail_price_usd": null,
"discount_vs_retail_pct": null,
"condition": {
"id": 17,
"slug": "used",
"name": "Used"
},
"sport": "hockey",
"category_slug": "sticks",
"url": "https://sidelineswap.com/gear/hockey/sticks/12713021-bauer-youth-vapor-hyperlite-hockey-stick-right-handed-p28-30-flex-used",
"image": {
"id": 97577225,
"url": "https://images.sidelineswap.com/production/097/577/225/b3c6a3cef421dc07_original.jpeg",
"thumb_url": "https://images.sidelineswap.com/production/097/577/225/b3c6a3cef421dc07_original.jpeg"
},
"seller": {
"id": 1160618,
"username": "ReQuip",
"url": "https://sidelineswap.com/ReQuip",
"emblems": [
"[trimmed-depth]",
"[trimmed-depth]"
],
"badges": [],
"feedback_score": null,
"feedback_count": null,
"avatar_url": null
},
"favorites": 2,
"views": 63,
"label": null,
"sold_via_offer": null,
"listed_at": "2026-08-16T16:29:01.000-04:00",
"updated_at": "2026-10-07T04:52:17.000-04:00"
},
{
"id": 12373114,
"name": "Junior CCM Super Tacks AS-V Pro Right Handed Hockey Stick P29 50 Flex Pro Stock (Used)",
"state": "available",
"price_usd": 50,
"list_price_usd": 50,
"retail_price_usd": null,
"discount_vs_retail_pct": null,
"condition": {
"id": 17,
"slug": "used",
"name": "Used"
},
"sport": "hockey",
"category_slug": "sticks",
"url": "https://sidelineswap.com/gear/hockey/sticks/12373114-ccm-junior-super-tacks-as-v-pro-right-handed-hockey-stick-p29-50-flex-pro-stock-used",
"image": {
"id": 94831685,
"url": "https://images.sidelineswap.com/production/094/831/685/ad330f9337c0faa8_original.jpeg",
"thumb_url": "https://images.sidelineswap.com/production/094/831/685/ad330f9337c0faa8_original.jpeg"
},
"seller": {
"id": 666556,
"username": "Peter15",
"url": "https://sidelineswap.com/Peter15",
"emblems": [
"[trimmed-depth]"
],
"badges": [],
"feedback_score": null,
"feedback_count": null,
"avatar_url": null
},
"favorites": 11,
"views": 82,
"label": null,
"sold_via_offer": null,
"listed_at": "2026-06-18T12:37:06.000-04:00",
"updated_at": "2026-10-06T04:52:17.000-04:00"
}
]
}
}What the SidelineSwap API does
| Action | Description | Concrete use case | Key params |
|---|---|---|---|
| search | Search SidelineSwap's used and new sporting-goods listings across 36 sports (450,265 live listings measured 2026-10-07), or the 2,086,728 SOLD listings with the price they actually transacted at. Filter by sport or category, brand, model, condition, handedness, pro-stock vs retail, price, seller type, seller region, US ZIP proximity and the source's own category-specific attributes (hockey-stick flex, curve pattern, stick length, ski boot size, soccer size…). Needs no account and no key. Pass at least one of query, category, model or seller unless you really want the whole catalogue. | Pricing teams call search to search SidelineSwap's used and new sporting-goods listings across 36 sports (450,265 live lis…. | query, category, detail, condition, hand, ... |
| detail | One listing in full, as the item page publishes it: the seller's own description, every category-specific attribute as its OWN field (flex, pattern, hand, stick length, age group, boot size…), condition tier, asking price against retail price, parcel dimensions and weight, shipping and delivery estimate, all photos, offer and auto-price-drop settings, and the full seller block with badges and feedback totals. Works for sold listings too, where `price_usd` is the transacted price and `sold_via_offer` says whether it went through an accepted offer. | Marketplace operators call detail to get one listing in full, as the item page publishes it. | id, include_pii |
| comps | Sold-price comparables: what gear of this kind ACTUALLY sold for on SidelineSwap, not what it is being asked for. Reads the source's sold side (2,086,728 sold listings) for one model, category or search term and returns min / p25 / median / p75 / max / mean over the sample plus the live-side statistics for the same query, so an asking price can be read against the transacted ones. Each sold row keeps its own price, condition, date and whether the sale went through an accepted offer. Pass at least one of model, category or query. | Catalog enrichment teams call comps to get sold-price comparables. | model, category, query, detail, brand, ... |
| categories | SidelineSwap's own category tree: 36 listable top-level sports and 806 nodes in all. This is where the numeric category ids that search, comps, filters and models take come from. Call it with no parameters for the sports, or with a sport to walk into it (hockey has 16 children). | Retail analysts call categories to get sidelineSwap's own category tree. | category, depth, include_pii |
| filters | The attribute taxonomy SidelineSwap itself uses for one category — the lookup table behind the `detail` parameter of search and comps. For hockey sticks it returns 13 groups including Flex (88 values), Pattern (64 curves), Stick Length (48), Hand, Age Group, Pro Stock, Is This Stick Cut, Stick Pack and Brand (65); for skiing it returns Boot Size; for soccer, Size. Each value carries the id to pass back. Which groups exist depends on the category, which is why this is a per-category lookup and not one global list. | Pricing teams call filters to get the attribute taxonomy SidelineSwap itself uses for one category. | category, include_pii |
| models | The model catalogue for a category: the named products SidelineSwap groups listings under, with the brand, the source's own GTIN/MPN where it has them, how many are available and how many have sold, and the model's live price range. This is where the `model` id for search and comps comes from. Note the source wants `brand_id` on this endpoint while search wants `brand` — the engine sends each the name that one actually accepts; `brand` here is ignored upstream and would quietly return every brand's models. | Marketplace operators call models to get the model catalogue for a category. | category, brand, query, include_pii |
| seller | A seller's public marketplace profile: username, badges and emblems, feedback score with the positive/neutral/negative split, sales and follower counts, the region and country they ship from, and their live/sold/draft listing counts. Optionally their current listings and their received feedback comments. Public profile data only — the source publishes no name, street address, phone or e-mail on this surface and this engine does not go looking for any. | Catalog enrichment teams call seller to get a seller's public marketplace profile. | username, include_items, include_feedback, page_size, include_pii |
Call search from your stack
curl -X POST https://api.reefapi.com/sidelineswap/v1/search \
-H "x-api-key: $REEF_KEY" \
-H "content-type: application/json" \
-d '{"category":"110023","detail":[76],"condition":["used"],"min_price":50,"sort":"price_asc","page_size":20}'import requests
r = requests.post(
"https://api.reefapi.com/sidelineswap/v1/search",
headers={"x-api-key": REEF_KEY},
json={
"category": "110023",
"detail": [
76
],
"condition": [
"used"
],
"min_price": 50,
"sort": "price_asc",
"page_size": 20
},
)
print(r.json()["data"])const res = await fetch("https://api.reefapi.com/sidelineswap/v1/search", {
method: "POST",
headers: {
"x-api-key": process.env.REEF_KEY,
"content-type": "application/json",
},
body: JSON.stringify({
"category": "110023",
"detail": [
76
],
"condition": [
"used"
],
"min_price": 50,
"sort": "price_asc",
"page_size": 20
}),
});
const { ok, data, meta, error } = await res.json();Ask your MCP-connected assistant: call reefapi.sidelineswap.search with {"category":"110023","detail":[76],"condition":["used"],"min_price":50,"sort":"price_asc","page_size":20}.Who uses this API and why
- Price used gear before listing or buying it: pull `comps` for the exact model and read the sold median, quartiles and spread against what is currently being asked, instead of guessing from live listings alone.
- Build a resale price index per sport, brand and model - 2,086,777 sold listings with transacted prices, model ids that group them, and available and sold counts on every model record.
- Source inventory for a shop or reseller: filter by category, brand, condition, pro stock, price ceiling, price drops and seller region, sort by newest, and page through with the source's own honest totals.
- Match gear to a player's specification rather than a search string: handedness, flex, curve pattern, stick length, age group, ski boot size or soccer size as real filters with the source's own ids.
- Watch a competitor's or a partner's storefront: one seller's live listings, their feedback split, sales count, badges and shipping region, refreshed as often as you like.
Questions developers ask before integrating
Do I need a SidelineSwap account or key?
No. Everything this API reads is the public marketplace: search, listings, the category and attribute taxonomies, model catalogues and public seller profiles. Nothing behind a login is touched, so there are no credentials to manage and nothing to keep warm.
Can I get the price gear actually sold for, not just asking prices?
Yes, and that is the main reason to use this one. SidelineSwap keeps 2,086,777 sold listings with the price each transacted at, and `comps` turns them into min, p25, median, p75, max and mean for a model or category, next to the same statistics for what is currently listed. Bauer Proto2 hockey sticks on 2026-10-08: 912 sold, sample median $221.10, against a live median of $245.00 over 253 available - a sold-to-asked ratio of 90.2 %. Each sold row also says whether the sale went through an accepted offer.
How do I filter by hockey-stick flex, curve or handedness?
Call `filters` on the category you are searching - for Hockey > Sticks (id 110023) it returns 13 attribute groups including Flex with 88 values, Pattern with 64 curves, Stick Length with 48, Hand, Is This Stick Cut, Stick Pack and Age Group - then pass the ids you want to `search` as `detail`. Handedness and condition also have plain named parameters (`hand`, `condition`) because their ids are the same in every sport. One thing to know: attributes belong to the exact category that carries them, so a stick attribute filters under Hockey > Sticks and not under Hockey as a whole.
Which sports and how much of each?
36 listable sports, 806 category nodes. Measured live counts on 2026-10-08: golf 107,432, hockey 87,319, baseball 73,072, apparel 56,226, footwear 42,512, lacrosse 21,776, skiing 21,548, softball 21,091, football 11,951, tennis and racket sports 11,051, snowboarding 9,575, soccer 8,207, memorabilia 7,205, inline and roller hockey 3,467, basketball 2,894, bikes 2,370, fitness 2,118, women's lacrosse 1,844, disc golf 1,630, wrestling 1,096, equestrian 682, motocross 416, fishing 255, paintball 74, plus smaller ones. Those add up to more than the 452,454 total because a listing can sit in more than one category.
Is pro-stock gear separated from retail?
Yes, it is a real split on this source and a filter of its own. Pro stock means team-issued equipment that was never a retail SKU, and 11,719 of 19,268 hockey sticks are flagged as it against 2,920 flagged retail. `pro_stock` takes `pro_stock` or `retail`.
Can I follow one seller, or find gear near me?
Both. `search` takes a `seller` username for everything one seller has listed, and `seller` returns their public profile - badges, feedback split, sales count, ships-from region and live/sold counts - with their current listings and, optionally, the feedback buyers left. For location, `near_zip` takes a 5-digit US ZIP and narrows to sellers near it (20146 cut 452,454 to 66,402), and `seller_location` filters on the source's own regions, including Canada.
How fresh is the data, and are sold items mixed into live results?
Every call reads SidelineSwap live - there is no cached copy behind this API. Listings carry their own `listed_at` and `updated_at`. Sold items are a separate mode (`state`), but the live feed does keep a just-sold listing for a short while: measured at 1 and 2 rows per 200 under the default order and 0 per 200 under `newest`. Every row carries its own `state` and each response counts the overlap, so you can drop them or keep them deliberately.
What does this API not give me?
A confirmed sale date - sold rows carry `updated_at`, which is when the record last changed, and we do not relabel it as a sale timestamp. No buyer identity and no offer history, only the accepted-offer flag. No seller name, address, phone or e-mail, because the public surface has none. A per-listing shipping price exists on about 1 in 48 listings; the feed shipping cost and the measured parcel dimensions are there instead. Barcodes are sparse - 8 of 40 on new-condition gear, near zero on used. And nothing that needs an account: watch-lists, messages, offers and checkout are all out of scope.
What is the SidelineSwap API?
SidelineSwap API is a ReefAPI endpoint group for used sporting goods across 36 us sports: listings with brand, model, condition, size, flex, curve and handedness as their own fields, plus the prices gear actually sold for. It returns live JSON through POST requests under /sidelineswap/v1.
Is the SidelineSwap API free to try?
Yes. ReefAPI starts with 1,000 free credits, no card required. SidelineSwap calls use the same shared credit balance as every other ReefAPI engine.
Do I need a SidelineSwap login or account?
No login to SidelineSwap is needed for the API response. You call ReefAPI with your x-api-key header, and the playground can run live examples before you create a production key.
How fresh is the SidelineSwap data?
The page example is captured from a live search call, and production requests fetch live data through ReefAPI rather than a static sample.
How many credits does the SidelineSwap API use?
SidelineSwap actions currently cost 1-3 credits per successful call. Failed or blocked calls are free. All APIs draw from one credit pool.
Can I call SidelineSwap from an AI assistant or MCP client?
Yes. Connect ReefAPI once through MCP and your assistant can call sidelineswap actions with the same key, credit pool and JSON envelope used by normal REST requests.