CompSniper Docs

Complete API reference

Full CompSniper endpoint, parameter, sold-item, active-item, response, and error reference.

Searchable endpoint index

MethodEndpointPurpose
GET/v1/scrapeKeyword sold search or active listing search
GET/v1/scrape/categoryBrowse sold listings by eBay category
GET/v1/summaryCleaned price intelligence summary
GET/v1/account/usagePlan, quota, and remaining usage
POST/v1/cards/batchStart an asynchronous card-pricing batch
GET/v1/cards/batch/:jobIdPoll a card-pricing batch
DELETE/v1/cards/batch/:jobIdCancel unfinished card work
POST/v1/scrape/maxStart Max Mode
GET/v1/scrape/max/results/:jobIdPoll Max Mode results
DELETE/v1/scrape/max/:jobIdCancel Max Mode

Keyword search parameters

keyword, page, count, ebaySite, categoryId, sortOrder, minPrice, maxPrice, itemLocation, itemCondition, conditionId, buyingFormat, sellerType, includeCompleteListing, sold, soldAfter, soldBefore, aspectFilter, and relevance.

Sold item fields

itemId, url, thumbnailUrl, fullResThumbnailUrl, epid, title, condition, conditionId, sellerType, buyingFormat, bestOfferAccepted, bidCount, categoryId, listingType, shippingPrice, shippingCurrency, shippingType, totalPrice, sellerUsername, sellerPositivePercent, sellerFeedbackScore, itemLocation, scrapedAt, endedAt, soldPrice, and soldCurrency.

Active listing fields

With sold=false, sold-only fields are replaced by currentPrice, currentPriceMax, currentCurrency, watcherCount, unitsSold, acceptsOffers, and timeLeft.

The detailed reference below preserves all examples, tables, response envelopes, MCP instructions, and batch documentation from the original single-page reference.

Introduction

Overview

CompSniper turns any keyword into up to 240 completed eBay sales in a single request. Each listing carries the sold price, sale date, condition, shipping, and seller reputation across 26 fields of real transaction data, not estimates or asking prices.

All requests go to https://api.compsniper.com and are versioned under /v1. Responses are always JSON. Prices are returned as decimal strings to avoid floating-point rounding.

Quick start: first response in three steps

Pull 10 sold iPhone 15 Pro listings. Each tab below is complete: replace the placeholder with the key from your dashboard, then run it as written.

  1. 01

    Copy your API key

    Get it from Dashboard → API keys.

  2. 02

    Replace one placeholder

    Replace cs_live_REPLACEWITHYOURKEY with the full key.

  3. 03

    Run and read JSON

    A successful response includes sold rows, price intelligence, and remaining-quota headers.

curl --fail-with-body -H "Authorization: Bearer cs_live_REPLACEWITHYOURKEY" "https://api.compsniper.com/v1/scrape?keyword=iphone+15+pro&count=10"

8 marketplaces

US, UK, DE, FR, IT, ES, CA, AU

240 items / request

One page per call, up to 240 listings each.

90 days of history

The completed-sales window eBay exposes.

Introduction

Copy-paste API requests

These are complete one-line cURL commands, so there are no line-continuation characters to break when pasting into a terminal. Replace the API-key placeholder where shown; the health request works without a key.

Check service health

No API key required. Use this before a scheduled import or when diagnosing an outage.

cURL
curl --fail-with-body "https://api.compsniper.com/v1/status"

Get a price summary

Return the median, realistic p25-p75 range, sample size, and raw comparison without listing rows.

cURL
curl --fail-with-body -H "Authorization: Bearer cs_live_REPLACEWITHYOURKEY" "https://api.compsniper.com/v1/summary?keyword=shure+sm7b&count=240"

Search with filters

Pull used items from $150 to $350 while excluding accessories in the keyword.

cURL
curl --fail-with-body -H "Authorization: Bearer cs_live_REPLACEWITHYOURKEY" "https://api.compsniper.com/v1/scrape?keyword=shure+sm7b+-stand+-cable&itemCondition=used&minPrice=150&maxPrice=350&count=50"

Search another marketplace

Use the same response shape across all eight supported eBay marketplaces.

cURL
curl --fail-with-body -H "Authorization: Bearer cs_live_REPLACEWITHYOURKEY" "https://api.compsniper.com/v1/scrape?keyword=iphone+15+pro&ebaySite=ebay.co.uk&itemCondition=used&count=50"

Return raw eBay matches

Disable AI relevance filtering when you want every upstream row, including accessories and nearby models.

cURL
curl --fail-with-body -H "Authorization: Bearer cs_live_REPLACEWITHYOURKEY" "https://api.compsniper.com/v1/scrape?keyword=iphone+15+pro&relevance=false&count=240"

Check account usage

Read the plan, monthly quota, remaining requests, reset period, and per-minute limit.

cURL
curl --fail-with-body -H "Authorization: Bearer cs_live_REPLACEWITHYOURKEY" "https://api.compsniper.com/v1/account/usage"

Want a complete working project?

Follow the Python tutorial or clone the public Python, Node.js, cURL, retry, pagination, and Card Batch examples.

Introduction

Authentication

Every request is authenticated with a bearer token. Send your key in the Authorization header. CompSniper keys always start with cs_live_ and are created in your dashboard.

The free Basic plan includes 100 requests per month with no credit card. Treat your key like a password: it grants full access to your quota, so keep it server-side and never commit it to a public repository.

Authenticated request
curl --fail-with-body -H "Authorization: Bearer cs_live_REPLACEWITHYOURKEY" "https://api.compsniper.com/v1/scrape?keyword=stanley+tumbler"

Where do keys come from?

Create a free account to get an instant key, then rotate or revoke it anytime from the dashboard. Requests without a valid key return 401 unauthorized.

Introduction

Rate limits

Two independent limits apply to every key. The first is a per-minute rate limit (60 requests per minute on standard plans). The second is your monthly request quota, set by your plan. Hitting either one returns 429, but with a different code so you can tell them apart.

Exceeding the per-minute limit returns rate_limited and is temporary: retry after the Retry-After header (in seconds). Exhausting your monthly quota returns quota_exceeded and blocks further requests until your billing cycle resets. Basic accounts associated with the same free-plan identity share one allowance, so creating another account does not create another quota.

429 · rate_limited

Retry later

Wait the number of seconds in Retry-After, add jitter, and stop after five attempts.

429 · quota_exceeded

Do not retry

Stop immediately. Show upgrade_url or wait until reset_at. Retrying only creates more 429 errors.

Rate and usage headers ship with authenticated search responses and limit errors, so you can slow down before receiving a 429:

Response headers
HTTP/1.1 200 OK
Content-Type: application/json
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 58
X-RateLimit-Reset: 1765843200
X-Usage-Limit: 10000
X-Usage-Remaining: 8412
X-RateLimit-Limit / -Remaining

Requests allowed and left in the current per-minute window.

X-RateLimit-Reset

Unix epoch (seconds) when the per-minute window resets.

X-Usage-Limit / -Remaining

Requests allowed and left in your monthly plan quota.

Retry-After

Seconds to wait before retrying a temporary rate_limited response.

Never blindly retry every 429

Read the response code first. Retry rate_limited only after Retry-After, with a small random jitter and a maximum attempt count. Never retry quota_exceededin a loop. Stop the job and ask the user to upgrade or wait for reset_at.

Copy one of these bounded retry helpers. Both monitor remaining monthly usage, respect the server delay, add jitter to prevent synchronized retries, stop immediately on quota exhaustion, and retry temporary 5xx failures without looping forever.

import random
import time
import requests

API_KEY = "cs_live_REPLACEWITHYOURKEY"

def search_sold(keyword: str):
    for attempt in range(5):
        response = requests.get(
            "https://api.compsniper.com/v1/scrape",
            headers={"Authorization": f"Bearer {API_KEY}"},
            params={"keyword": keyword, "count": 240},
            timeout=60,
        )
        body = response.json()

        if response.ok:
            print("Requests remaining:", response.headers.get("X-Usage-Remaining"))
            return body

        if response.status_code == 429 and body.get("code") == "quota_exceeded":
            raise RuntimeError(
                f"Monthly quota exhausted. Reset: {body.get('reset_at')}. "
                f"Upgrade: {body.get('upgrade_url')}"
            )  # Do not retry this request.

        if response.status_code == 429 and body.get("code") == "rate_limited":
            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

        if response.status_code in (500, 502, 503):
            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

        response.raise_for_status()

    raise RuntimeError("CompSniper request failed after 5 bounded retries")

data = search_sold("9780399145636")
print(data["totalItems"], "sold listings")
Introduction

Errors

CompSniper uses conventional HTTP status codes. Every error body is JSON with a human-readable error string and a stable machine-readable code. Rate and quota errors add retry_after or reset_at so you know when to try again.

StatusCodeMeaningWhat to do
400invalid_paramsA query parameter is missing or malformed.Check the parameter table below. Most often keyword is missing or count is outside 1-240.
401unauthorizedThe API key is missing, malformed, or revoked.Send a valid Authorization: Bearer header. Keys start with cs_live_ and live in your dashboard.
403unauthorizedThe credential is valid but does not have the scope required by this route.Reconnect the OAuth client with the requested scope, or use an API key created in your dashboard.
404not_foundThe requested asynchronous job does not exist, expired, or belongs to another account.Check the jobId and API key. Card batch results expire after 30 days.
409conflictAnother Max Mode or card batch job is already running for this account.Poll or cancel the active job before submitting another. Reuse an Idempotency-Key when retrying a submission.
429rate_limitedYou exceeded your per-minute request limit.Wait for Retry-After, add a small jitter, and retry with a bounded attempt count. Standard plans allow 60 requests per minute.
429quota_exceededYou used every request in your plan. Free accounts associated with the same identity share one allowance.Do not retry in a loop. Stop the job, then wait for reset_at or send the user to upgrade_url. This limit is independent of the per-minute limit.
500server_errorAn unexpected error occurred on our side.Retry with backoff. If it persists, email [email protected].
502upstream_blockedeBay blocked or refused the upstream fetch.Transient. Retry with backoff. We rotate infrastructure automatically, so most retries succeed.
503server_busyThe scraping service is temporarily at capacity.Respect Retry-After, reduce parallel requests, and use a bounded retry count.

When contacting support, include the HTTP status, response code, and the X-Request-ID response header when present. Never send your full API key.

429 · rate_limited

Error · JSON
{
  "error": "Per-minute rate limit exceeded.",
  "code": "rate_limited",
  "retry_after": 12
}

429 · quota_exceeded

Error · JSON
{
  "error": "Free plan quota exhausted. Upgrade your CompSniper plan to continue now, or wait for your monthly reset.",
  "code": "quota_exceeded",
  "plan": "basic",
  "quota": 100,
  "used": 100,
  "remaining": 0,
  "reset_at": "2026-09-18T05: 30: 36.000Z",
  "upgrade_url": "https://compsniper.com/dashboard/subscription"
}
Introduction

Pagination

Walk through results with the page parameter, starting at 1. Each response reports whether more pages exist via hasNextPage. Keep incrementing page until it is false. Each page you fetch debits one request from your quota.

Do not confuse the two count fields. totalItems is the number of listings on the current page (up to 240), while totalResults is eBay’s own reported match count as a string (for example "14,000+"), and is null when eBay does not report one.

Page 2 · JSON
{
  "keyword": "iphone 15 pro",
  "page": 2,
  "totalItems": 240,
  "totalResults": "14,000+",
  "hasNextPage": true,
  "autoSelectedCategory": { "id": "9355", "name": "Cell Phones & Smartphones" },
  "items": [ /* ... */ ]
}
eBay

Keyword sold search

The core endpoint. Pass a keyword and get back completed eBay sales. Every other parameter is optional and maps directly to an eBay search filter, so you can narrow 240 results down to exactly the comps you need.

GET/v1/scrape

Query parameters

keywordstring·required
Search terms, exactly as you would type them into eBay. Supports the eBay minus-sign exclusion (for example: iphone 15 -case) to drop unwanted results.
pageinteger·1
Result page to return. Combine with hasNextPage to walk through the full result set.
countinteger·240
Number of listings to return, from 1 to 240. One request returns up to one page regardless of count.
ebaySiteenum·ebay.com
Which of the 8 eBay marketplaces to search. One of ebay.com, ebay.co.uk, ebay.de, ebay.fr, ebay.it, ebay.es, ebay.ca, ebay.com.au.
categoryIdstring·0
eBay category id (the _sacat value). 0 searches all categories. Browse the full list of 17,000+ ids at /ebay-categories.
sortOrderenum·endedRecently
Result ordering. One of endedRecently, timeNewlyListed, pricePlusPostageLowest, pricePlusPostageHighest, distanceNearest.
minPricenumber·null
Lower bound on total price (item plus shipping), in the marketplace currency.
maxPricenumber·null
Upper bound on total price (item plus shipping), in the marketplace currency.
itemLocationenum·default
Seller location filter. One of default, domestic, worldwide.
itemConditionenum·any
High-level condition filter. One of any, new, used.
conditionIdinteger·null
Numeric eBay condition id (for example 1000 for New, 3000 for Used). Overrides itemCondition when set.
buyingFormatenum·all
Listing format filter. One of all, auction, buyItNow, acceptsOffers.
sellerTypeenum·null
Filter by private or business sellers. Available on EU marketplaces only.
includeCompleteListingboolean·true
When true, restricts to completed listings (LH_Complete=1) and enables the bestOfferAccepted signal on each item.
soldboolean·true
When true, returns completed sales. Set false to return currently active listings (swaps the sold fields for the active fields).
soldAfterstring·null
Keep only sales on or after this date (YYYY-MM-DD). Applied as a post-parse filter, so scrapedCount appears in the envelope.
soldBeforestring·null
Keep only sales on or before this date (YYYY-MM-DD). Applied as a post-parse filter.
aspectFilterstring·null
URL-encoded JSON of eBay sidebar facet names (for example brand or storage size) to narrow results the way the eBay left rail does.

Response envelope

The top-level object wraps the result set with pagination metadata and eBay’s auto-selected category. Listings live in the items array.

Response · JSON
{
  "keyword": "iphone 17 pro max",
  "page": 1,
  "totalItems": 240,
  "totalResults": "14,000+",
  "hasNextPage": true,
  "autoSelectedCategory": {
    "id": "9355",
    "name": "Cell Phones & Smartphones"
  },
  "items": [
    {
      "itemId": "256123456789",
      "url": "https://www.ebay.com/itm/256123456789?nordt=true",
      "thumbnailUrl": "https://i.ebayimg.com/images/g/3nkAAeSw/s-l500.webp",
      "epid": "20049285656",
      "title": "Apple iPhone 17 Pro Max 256GB Black Titanium - Unlocked",
      "condition": "Pre-Owned",
      "conditionId": 3000,
      "buyingFormat": "buyItNow",
      "bestOfferAccepted": false,
      "bidCount": null,
      "categoryId": "9355",
      "listingType": "sold",
      "endedAt": "2026-03-10",
      "soldPrice": "1245.00",
      "soldCurrency": "USD",
      "shippingPrice": "0.00",
      "shippingCurrency": "USD",
      "shippingType": "free",
      "totalPrice": "899.99",
      "sellerUsername": "top-deals-store",
      "sellerPositivePercent": 99.8,
      "sellerFeedbackScore": 14200,
      "itemLocation": "United States",
      "scrapedAt": "2026-03-14T21: 00: 00.000Z"
    }
    // ... 239 more items
  ]
}
keywordThe keyword you searched, echoed back.
pageThe page number returned.
totalItemsListings on this page (up to 240).
totalResultseBay’s reported match count as a string, or null.
hasNextPageWhether another page is available.
autoSelectedCategoryThe category eBay auto-picked, or null.
scrapedCountPresent only when date-filtering with soldAfter or soldBefore.
itemsArray of listing objects (schema below).

Item schema (26 fields, sold mode)

Each object in items when sold=true (the default). Prices are decimal strings. On Best Offer sales, soldPrice is an upper bound because eBay never discloses the accepted offer amount.

itemIdstring
eBay item id.
urlstring
Canonical listing URL (appends ?nordt=true).
thumbnailUrlstring | null
Standard resolution image (s-l500).
fullResThumbnailUrlstring | null
Full resolution image (s-l1600).
epidstring | null
eBay product identifier, when the listing is catalog-matched.
titlestring | null
Listing title as shown on eBay.
conditionstring | null
Localized condition label (for example Pre-Owned).
conditionIdnumber | null
Numeric eBay condition id.
sellerTypestring | null
private or business (EU marketplaces).
buyingFormatstring | null
auction, buyItNow, auctionWithBIN, or null.
bestOfferAcceptedboolean
True when the sale closed via an accepted Best Offer.
bidCountnumber | null
Number of bids (auctions only).
categoryIdstring | null
eBay category id the listing sold under.
listingTypestring
sold or active.
shippingPricestring | null
Shipping cost as a decimal string.
shippingCurrencystring | null
ISO 4217 currency for shipping.
shippingTypestring | null
free, paid, pickup, or unknown.
totalPricestring | null
Sold price plus shipping, as a decimal string.
sellerUsernamestring | null
Seller handle.
sellerPositivePercentnumber | null
Seller positive feedback percentage.
sellerFeedbackScorenumber | null
Seller total feedback score.
itemLocationstring | null
Seller location. Null when domestic to the marketplace.
scrapedAtstring
ISO 8601 timestamp of when the data was fetched.
endedAtstring | null
Sale date (YYYY-MM-DD).
soldPricestring | null
Sale price as a decimal string. On Best Offer sales this is an upper bound.
soldCurrencystring | null
ISO 4217 currency for the sale price.

Active mode (sold=false)

Set sold=false to return currently active listings instead. The shared fields stay the same, but the sold fields (endedAt, soldPrice, soldCurrency) are replaced by these:

currentPricestring | null
Current asking price (or lower bound of a range).
currentPriceMaxstring | null
Upper bound when the listing shows a price range.
currentCurrencystring | null
ISO 4217 currency for the current price.
watcherCountnumber | null
Number of watchers on the listing.
unitsSoldnumber | null
Units already sold on a multi-quantity listing.
acceptsOffersboolean
True when the listing accepts Best Offers.
timeLeftstring | null
Raw localized time-remaining string from eBay.
eBay

Category browse

Browse an entire eBay category without a keyword. Same request shape and same response envelope as /v1/scrape, except categoryId is required and the response keyword is an empty string. Every other filter (minPrice, itemCondition, sortOrder, and the rest) works identically.

Find category ids at /ebay-categories, which lists all 17,000+ of them.

GET/v1/scrape/category
Terminal
curl -H "Authorization: Bearer cs_live_REPLACEWITHYOURKEY" \
  "https://api.compsniper.com/v1/scrape/category?categoryId=9355&count=100"

Required parameter

categoryId (string) is required. All other parameters from the GET /v1/scrape table are supported, with the exception of keyword.

eBay

Batch card pricing

Submit up to 100 sports cards or trading cards in one HTTP request. CompSniper builds precise search keywords from structured card fields, processes the cards as a background job, and preserves your reference value in every result. You can also provide an exact keyword when you already know the query you want.

Each successfully processed card consumes one normal monthly request. Processing is sequential, cached results are reused, quota and upstream failures stop the unfinished portion safely, and completed results remain available for 30 days.

POST/v1/cards/batch
Submit · cURL
curl -X POST "https://api.compsniper.com/v1/cards/batch" \
  -H "Authorization: Bearer cs_live_REPLACEWITHYOURKEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: inventory-run-2026-08-29" \
  -d '{
    "cards": [
      {
        "reference": "inventory-001",
        "year": 2023,
        "set": "Topps Chrome",
        "player": "Victor Wembanyama",
        "cardNumber": "1",
        "parallel": "Refractor",
        "grader": "PSA",
        "grade": 10
      },
      {
        "reference": "inventory-002",
        "keyword": "1999 pokemon base set charizard 4/102 psa 9"
      }
    ],
    "options": {
      "count": 60,
      "outputMode": "summary",
      "relevance": true
    }
  }'

Card fields

Use subject for any card, or the convenient aliases player and character. Add year, set, cardNumber, parallel, grader, and grade to narrow the generated query. An explicit keyword overrides those generated fields.

Poll or cancel

Poll · cURL
curl -H "Authorization: Bearer cs_live_REPLACEWITHYOURKEY" \
  "https://api.compsniper.com/v1/cards/batch/JOB_ID"
  • GET /v1/cards/batch/:jobId returns progress plus every completed card result.
  • DELETE /v1/cards/batch/:jobId safely stops the unfinished portion.
  • outputMode: summary supports 100 cards; compact also returns sold rows and is capped at 25 cards with 60 rows each.
  • Each compact sold row includes bestOfferAccepted: true when eBay marked an accepted Best Offer, otherwise null. Because eBay does not disclose the accepted amount, exclude rows where the value is true when exact sale price matters.
  • Use Idempotency-Key when retrying submissions so network retries cannot create duplicate jobs.
More

MCP connector for Claude, ChatGPT, and more

Query eBay sold prices straight from your AI tools. The CompSniper MCP server plugs into Claude, ChatGPT, Codex, Cursor, Claude Code, and other MCP clients, so you can just ask “what does a used iPhone 14 Pro sell for?” and get the median, the realistic range, and the comps behind it. Hosted clients connect through secure CompSniper sign-in, while local clients can continue using an API key.

Tools

  • search_sold_comps - sold listings for a keyword plus a price summary.
  • get_price_summary - just the priced answer (median, range, average, sample size).
  • Batch and comparison tools - price product lists or compare marketplaces.
  • Usage and status tools - inspect quota, rate limits, and service health.
  • Max Mode tools - start, poll, cancel, and export deep searches.

Search tools support the 8 marketplaces, condition, and price filters, and eBay search operators like -case.

Add to your client

For hosted clients, add the URL below and sign in to CompSniper when prompted. Your password and API keys are never shared with the AI client.

https://mcp.compsniper.com/mcp

For local Claude Desktop, Claude Code, or Cursor setup, use the npm configuration:

mcp config
{
  "mcpServers": {
    "compsniper": {
      "command": "npx",
      "args": ["-y", "compsniper"],
      "env": { "COMPSNIPER_API_KEY": "cs_live_REPLACEWITHYOURKEY" }
    }
  }
}

Example prompts

What does a used Shure SM7B sell for on eBay? Remove irrelevant accessories.

A cleaned median, realistic range, raw comparison, sample size, and matching sold listings.

Compare used iPhone 15 Pro sold prices in the US, UK, and Germany.

A marketplace-by-marketplace comparison with median, range, sample size, and currency.

Price these five products and tell me which has the highest resale value.

A bounded batch search with independent results, failures, and quota usage for each product.

More

Max Mode & RapidAPI

Max Mode (async sweeps)

For large pulls, Max Mode runs server-side pagination as a background job so you do not have to loop through pages yourself. Submit once, poll for progress, then read the items inline or download a CSV. Each scraped page debits one request from your quota. One job runs at a time per account (a second submit returns 409 with the active job). Pass maxPages (up to 100) to cap it.

  • POST /v1/scrape/max - enqueue, returns jobId
  • GET /v1/scrape/max/results/:jobId - poll (5s); terminal: done / maxPages_reached / cancelled / failed
  • DELETE /v1/scrape/max/:jobId - cancel
  • GET /v1/scrape/max/:jobId/download.csv?token= - signed CSV

RapidAPI channel

Prefer to bill through RapidAPI? The same engine is available there with an identical response shape. Instead of the bearer token, authenticate with X-RapidAPI-Key and X-RapidAPI-Host. Everything else in this reference applies unchanged.

Machine-readable spec

A full OpenAPI document is available at https://api.compsniper.com/openapi.json for generating typed clients.

On this page