BUFF sale history is now available - POST /v1/archive/buff returns sale prices and daily total supply since September 2024.

How to Compare CS2 Skin Prices Across Marketplaces

A single cs2.sh GET /v1/prices/latest response carries BUFF, Youpin, CSFloat, Skinport, Steam, and C5Game prices for the same item under one market_hash_name, each stamped with its own updated_at and collected_at. The comparison is already normalised; what remains is knowing which fields are comparable.

Cross-market comparison in CS2 is normally an identity problem before it is a price problem. The same skin carries different internal ids on every marketplace, and reconciling them is most of the work. That reconciliation is what this endpoint removes.

What cs2.sh provides#

Property Value
Endpoints GET /v1/prices/latest (all items), POST /v1/prices/latest (up to 100 named items)
Marketplaces BUFF, Youpin, CSFloat, Skinport, Steam, C5Game
Identity One market_hash_name across all six, with marketplace ids in GET /v1/schema
Currency Always USD
Refresh Most sources about every 5 minutes
Access Available on all plans
Liquidity context GET /v1/liquidity/items, Scale or Enterprise

Know which fields exist where#

Source ask bid ask_volume bid_volume
buff yes yes yes yes
youpin yes yes yes yes
steam yes yes yes yes
csfloat yes yes yes no
c5game yes yes yes no
skinport yes no yes no

Skinport publishes no buy orders at all, and CSFloat and C5Game publish buy-order prices without counts. Any comparison built on bid_volume silently covers only three of the six marketplaces.

Skinport compensates with fields the others lack: max_ask, mean_ask, and median_ask describe the whole listing distribution, and 24h_history through 90d_history carry its own rolling sale statistics.

Request and compare#

bash
curl -X POST https://api.cs2.sh/v1/prices/latest \
  -H "Authorization: Bearer <<YOUR_API_KEY>>" \
  -H "Accept-Encoding: gzip" --compressed \
  -H "Content-Type: application/json" \
  -d '{"items": ["USP-S | Printstream (Factory New)"]}'
json
"items": {
  "USP-S | Printstream (Factory New)": {
    "buff":    { "ask": 109.72, "bid": 106.17, "collected_at": "2026-07-26T18:53:10.67Z" },
    "youpin":  { "ask": 108.24, "bid": 107.06, "collected_at": "2026-07-26T18:52:41.584Z" },
    "csfloat": { "ask": 107.99, "bid": 105,    "collected_at": "2026-07-26T18:53:05.134Z" }
  }
}

Iterate the source keys, skip null values, and take the lowest ask for a buy decision or the highest bid for a sell decision. Because base item source objects are dense, every source key is present once any source has data, so branch on null rather than on key existence.

The three rules that keep a comparison valid#

Compare like with like. An ask on one marketplace against a bid on another is not a gap, it is two different questions. The lowest ask across six sources is a purchase price. The highest bid is a sale price. The distance between those two is a real figure, but it is not "the spread" on any single marketplace.

Check when each side was observed. Sources refresh independently, so two prices in one response were not read at the same instant. Compare collected_at before differencing them, and reject anything past your tolerance. response_time is when the whole response was generated and says nothing about an individual source.

Do not mix settlement. Steam values settle to Steam Wallet funds rather than cash. A Steam number in a table with BUFF and CSFloat needs an explicit assumption held separately from the raw value, and that assumption is yours rather than the API's.

Add liquidity before acting on a gap#

A price gap on an item nobody trades is not an opportunity. GET /v1/liquidity/items returns a daily bucket and an estimated sale time for every item:

json
"USP-S | Printstream (Factory New)": {
  "liquidity": "extremely_liquid",
  "estimated_sale_time": "1 - 2 hours"
}

Buckets run from extremely_illiquid through unknown to extremely_liquid, recomputed daily from sale volume and traded value over rolling 30- and 90-day windows, weighted toward recent activity. Variants are scored independently from their base item.

Pair the bucket with ask_volume on the cheap side. A low price backed by one listing on an illiquid item is a different proposition from the same price backed by four hundred listings on a liquid one.

Reading the data correctly#

Variant source objects are sparse, unlike base items. A Doppler phase only carries the sources that actually have data for that exact phase, so cross-market variant comparison covers fewer marketplaces than the base item does, and a missing source is absence rather than a null value.

Fees, regional availability, payment methods, and transfer restrictions are outside the API. The lowest ask is a candidate, not a settled decision, and the arithmetic that turns it into a landed cost belongs in your own model.

Steam supports no variants at all, so it drops out of any phase-level comparison entirely.

Worked example: the cheapest place to buy, ranked#

Request the item, collect every non-null ask with its source and collected_at, and discard anything older than your freshness bound. Sort ascending and you have a ranked buy list.

Now qualify it. Attach ask_volume to each row so a single-listing price is visible as such, and attach the item's liquidity bucket so an illiquid item is not treated like a liquid one. For a sell decision, run the same process on bid, remembering that only BUFF, Youpin, and Steam tell you how many buy orders stand behind the price.

Full references: GET /v1/prices/latest, GET /v1/liquidity/items, GET /v1/schema for marketplace ids. Refresh rates: data coverage.