# CompSniper API reference

Companion to SKILL.md. Base URL `https://api.compsniper.com`. Auth `Authorization: Bearer cs_...`.

## Endpoint index

| Method | Endpoint | Purpose |
| --- | --- | --- |
| GET | `/v1/scrape` | Keyword sold search or active listing search |
| GET | `/v1/scrape/category` | Browse sold listings by eBay category |
| GET | `/v1/poshmark/sold` | Public Poshmark US sold listings (up to 48 per page) |
| GET | `/v1/mercari` | Public Mercari US sold or active listings (up to 100 per page) |
| GET | `/v1/summary` | Cleaned price intelligence summary |
| GET | `/v1/account/usage` | Plan, quota, and remaining usage |
| POST | `/v1/bulk-search` | Stream up to 20 independent keyword searches |
| GET | `/v1/bulk-search/:runId/download.csv` | Export one keyword or the combined bulk run |
| POST | `/v1/cards/batch` | Start an asynchronous card-pricing batch (up to 100 cards) |
| GET | `/v1/cards/batch/:jobId` | Poll a card-pricing batch |
| DELETE | `/v1/cards/batch/:jobId` | Cancel unfinished card work |
| POST | `/v1/scrape/max` | Start Max Mode (deeper async run) |
| GET | `/v1/scrape/max/results/:jobId` | Poll Max Mode results |
| DELETE | `/v1/scrape/max/:jobId` | Cancel Max Mode |
| GET | `/v1/scrape/max/:jobId/download.csv` | Download a signed Max Mode CSV |
| GET | `/v1/research/sold` | Up to 3 years of eBay sold history, listing-level rows, 50 per page |
| GET | `/v1/tcgplayer/sold` | TCGplayer completed sales and market price history |
| GET | `/v1/tcgplayer/listings` | TCGplayer active listings with seller details |
| POST | `/v1/shipping/rates` | US shipping-rate estimates (separate plan) |
| GET | `/v1/shipping/usage` | Independent Shipping Rates plan and quota |

## GET /v1/scrape parameter defaults

| Parameter | Type | Default |
| --- | --- | --- |
| keyword | string | required |
| page | integer | 1 |
| count | integer | 240 (max 240) |
| ebaySite | enum | ebay.com |
| categoryId | string | 0 |
| sortOrder | enum | endedRecently |
| sold | boolean | true |
| relevance | boolean | true |
| exactMatch | boolean | false |
| minPrice / maxPrice | number | none |
| itemLocation | default / domestic / worldwide | default |
| itemCondition | any / new / used | any |
| conditionId | integer | none |
| buyingFormat | all / auction / buyItNow / acceptsOffers | all |
| sellerType | private / business | none |
| includeCompleteListing | boolean | none |
| soldAfter / soldBefore | date | none |
| aspectFilter | string | none |

`count` is a ceiling. Valid responses can contain fewer rows after baseline listing hygiene, date filters, or relevance cleanup.

## Response body (ScrapeResponse)

- `keyword`, `page`, `totalItems`, `totalResults`, `hasNextPage`
- `autoSelectedCategory`: `{ id, name }` or null
- `rawMedian`, `rawSampleCount`: median and sample size after optional exact-section parsing but before relevance cleaning
- `items`: array of listing objects
- `summary`: `{ count, currency, median, mean, min, max, p25, p75, avgShipping }` or null

### Sold item fields (sold=true)
`itemId`, `url`, `thumbnailUrl`, `fullResThumbnailUrl`, `epid`, `title`, `condition`, `conditionId`, `sellerType` (private or business), `buyingFormat` (auction, buyItNow, auctionWithBIN), `bestOfferAccepted`, `bidCount`, `categoryId`, `listingType` (sold), `shippingPrice`, `shippingCurrency`, `shippingType` (free, paid, pickup, unknown), `totalPrice`, `sellerUsername`, `sellerPositivePercent`, `sellerFeedbackScore`, `productRating`, `productReviewCount`, `itemLocation`, `scrapedAt`, `endedAt`, `soldPrice`, `soldCurrency`.

### Active listing fields (sold=false)
The sold-only fields (`soldPrice`, `soldCurrency`, `endedAt`) are replaced by `currentPrice`, `currentPriceMax`, `currentCurrency`, `watcherCount`, `unitsSold`, `acceptsOffers`, and `timeLeft`. All shared fields above still apply.

## Marketplaces (ebaySite)

`ebay.com` (US), `ebay.co.uk` (UK), `ebay.de` (Germany), `ebay.fr` (France), `ebay.it` (Italy), `ebay.es` (Spain), `ebay.ca` (Canada), `ebay.com.au` (Australia). Only the marketplace changes; authentication, quota usage, response fields, and parsing stay the same.

## Poshmark and Mercari

`GET /v1/poshmark/sold` and `GET /v1/mercari` cover their US marketplaces and do not require customer marketplace accounts. They use dedicated response schemas because their native fields differ from eBay. Poshmark returns up to 48 public sold listings per page; Mercari returns up to 100 public sold or active listings per page. See https://compsniper.com/docs/marketplaces for filters, response fields, pagination, and limitations.

## 3-year history and TCGplayer

`GET /v1/research/sold` takes `keyword` (1 to 200 chars), `startDate` and `endDate` (YYYY-MM-DD, within the last 3 years, required), `ebaySite` (the eight sites above, default `ebay.com`), `sort` (`quantityAscending` default or `lastSoldDescending`), `limit` (`10`, `20`, `50`, default `50`), `page` (1 to 200), `singleUnitOnly` (`true`/`false`). Unknown parameters return 400. Each row is one listing: `recordType` `single_unit_listing` (has `soldPrice`, `soldDate`) or `listing_aggregate` (`soldUnits`, `averageSoldPrice`, `totalSales`, `lastSoldDate`). Prices are USD on every site; `condition` is null; `relevanceApplied` is false. Errors: 429 `history_rate_limited` (20 per minute), 502/503 `historical_unavailable` (retry with a bound). Full docs: https://compsniper.com/docs/sold-history

`GET /v1/tcgplayer/sold` takes `keyword` or `productId`, `count` (1 to 250, default 100), `condition`, `variant`, `language`, `history` (`none`, `month`, `quarter`, `annual`). It returns `product`, `otherMatches`, `items`, `priceHistory`, `summary`. `GET /v1/tcgplayer/listings` takes the same product and filters plus `page` (1 to 50) and `count` (1 to 50). Full docs: https://compsniper.com/docs/tcgplayer

Getting results: both match on every word. Use short keywords (brand plus model, or card name plus set and number), never a full listing title; widen the history date range; page past 50 rows with `page`; for TCGplayer send one product per call and reuse its `productId`.

## Errors

JSON shape: `{ error, code, hint?, retry_after?, reset_at?, plan?, quota?, used?, remaining?, rollover_balance?, credit_balance?, upgrade_url?, purchase_credits_url? }`.

| HTTP | code | Meaning and handling |
| --- | --- | --- |
| 401 | unauthorized | Missing or invalid `cs_` key |
| 400 | invalid_params | Fix the parameter and retry |
| 429 | rate_limited | Per-minute limit. Respect `Retry-After`, back off with jitter |
| 429 | quota_exceeded | Monthly quota and credits exhausted. Do not retry. Upgrade, buy credits, or wait for `reset_at` |
| 502 | upstream_blocked | eBay challenged the request. Short delay, then retry. Not charged |
| 503 | server_busy | Temporary capacity. Respect `Retry-After`, reduce parallelism, bounded retries |
| 502 | upstream_unavailable | Transient upstream failure. Bounded retry |
| 500 | server_error | Transient. Bounded retry |

## Response headers

`X-Plan`, `X-Usage-Limit`, `X-Usage-Used`, `X-Usage-Remaining`, `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, `X-Credit-Source`, `X-Rollover-Balance`, `X-Credit-Balance`, `X-Request-ID`. Successful uncached responses also include a `Server-Timing` header breaking down queue, lease, pacing, eBay fetch, parsing, cleanup, and total engine time.

## Plans and rate limits (public list pricing)

| Plan | Price | Monthly requests | Per-minute |
| --- | --- | --- | --- |
| Basic | $0 | 100 | 30 |
| Starter | $9 | 2,000 | 60 |
| Growth | $29 | 10,000 | 60 |
| Scale | $79 | 50,000 | 60 |
| 100K | $129 | 100,000 | 120 |
| 150K | $179 | 150,000 | 120 |
| 250K | $299 | 250,000 | 120 |
| 500K | $549 | 500,000 | 120 |

Purchased credits: $5.00 per 1,000 requests, from 1,000 to 1,000,000 in increments of 100, non-expiring. Successful searches use eligible rollover, then the subscription allowance, then purchased credits. `X-Credit-Source: credits` uses purchased credits first, then falls back to the subscription allowance when they run out. Credits do not fund Shipping Rates quotes.
