How to Get Steam Community Market Sale History
Updated
cs2.sh's
POST /v1/archive/steamreturns the same sale data that Steam draws in the price graph on a Community Market item page: a median sale price and a purchase count per bucket, daily from April 26, 2013 and hourly from May 9, 2026.
Steam publishes a sale graph on every Community Market item page, but it gives you no way to query it. There is no documented endpoint, the page-embedded series is throttled aggressively, and it arrives as a rendered graph payload rather than a stable schema. cs2.sh collects that native series for every tracked regular CS2 item and serves it as JSON.
What cs2.sh provides for Steam sales
| Property | Value |
|---|---|
| Endpoint | POST /v1/archive/steam |
| Coverage | Daily (1d) from April 26, 2013; hourly (1h) from May 9, 2026 |
| Intervals | 1h, 1d (no maximum request range on either) |
| Refresh | About 1-4x per day |
| Items per request | 100 |
| Variants | Not supported |
| Currency | Always USD |
| Access | Requires a Scale or Enterprise API key |
Two fields come back per bucket. price is Steam's median sale price for that bucket, and volume is the number of purchases in it. This is Steam's own statistic, not a cs2.sh calculation, which is what makes the series comparable with what a person sees on the item page.
Variants are unsupported here because the Steam Community Market does not distinguish a Doppler phase from its base item. A request naming ★ Karambit | Doppler (Factory New) - Phase 2 returns unsupported_variant rather than silently falling back to the base item.
Request the series
curl -X POST https://api.cs2.sh/v1/archive/steam \
-H "Authorization: Bearer <<YOUR_API_KEY>>" \
-H "Accept-Encoding: gzip" --compressed \
-H "Content-Type: application/json" \
-d '{
"items": [
"USP-S | Printstream (Factory New)"
],
"start": "2025-01-01",
"end": "2025-02-01",
"interval": "1d"
}'| Field | Type | Required | Description |
|---|---|---|---|
items |
string[] |
Yes | Regular market_hash_name values, max 100. Variants are not supported. |
start |
string |
Yes | YYYY-MM-DD or RFC3339, inclusive. |
end |
string |
No | YYYY-MM-DD or RFC3339, exclusive. Defaults to now. |
interval |
string |
Yes | 1h or 1d. The native Steam bucket interval. |
Because neither interval has a maximum request range, a single request can pull an item's entire history back to 2013. Accept-Encoding: gzip matters on those ranges: thirteen years of daily buckets across 100 items is a large response.
The response shape
{
"response_time": "2026-07-26T18:54:20.110090345Z",
"currency": "USD",
"start": "2025-01-01T00:00:00Z",
"end": "2025-02-01T00:00:00Z",
"interval": "1d",
"items": {
"USP-S | Printstream (Factory New)": {
"market_hash_name": "USP-S | Printstream (Factory New)",
"count": 31,
"data": [
{
"bucket": "2025-01-01T00:00:00Z",
"price": 150.23,
"volume": 7
}
]
}
}
}| Field | Type | Description |
|---|---|---|
items.<name>.count |
integer |
Number of buckets with data. |
items.<name>.data[] |
array |
Buckets, sorted ascending by bucket. |
data[].bucket |
string (date-time) |
Native Steam bucket start. |
data[].price |
number | null |
Steam median sale price, USD. |
data[].volume |
integer | null |
Steam purchase count. |
start / end |
string (date-time) |
Normalized UTC range actually queried. |
errors |
ItemError[] |
Per-item failures returned alongside successful results. |
bucket is present on every bucket, but price and volume are independently nullable: Steam's native bucket sometimes omits one. Check each field rather than assuming a returned bucket is complete.
Reading the data correctly
The price field is a median, and that has consequences. A median is not the price of any particular sale, so multiplying price by volume gives an approximation of traded value rather than exact gross value. It is also not comparable like-for-like with POST /v1/archive/csfloat, where price is the arithmetic average of that day's sales. Label the statistic wherever both appear.
Steam prices settle to Steam Wallet funds rather than cash. If a chart puts Steam beside a cash marketplace such as BUFF or CSFloat, keep the raw Steam value and store any conversion assumption separately.
Sale history and listing history answer different questions. This endpoint reports what items sold for. POST /v1/prices/history with sources: ["steam"] reports how the active ask (the lowest sell listing) and bid (the highest buy order) moved. Joining the two on bucket gives a seller premium (ask close against median sale price) or buyer pressure (bid close against the same median), and sample_count alongside volume tells you how much evidence sits behind each side.
A missing bucket is a gap, not a zero-purchase bucket. Forward-filling one before summing volume inflates activity in exactly the quiet periods where the error matters most.
When items are missing or fail
Requests return partial results rather than failing wholesale. A 200 response can carry both items and an errors[] array.
| Code | Meaning |
|---|---|
not_in_archive |
A valid item has no Steam archive rows. |
unsupported_variant |
A variant name was requested; Steam does not support variants. |
unknown_item |
The market_hash_name does not resolve. |
404 not_found |
None of the requested valid items have any Steam archive rows. |
Treat not_in_archive as a coverage fact about that item, not a request error. Retrying it produces the same result.
Worked example: a thirteen-year price chart
To chart one item's full Steam history, request interval: "1d" with start at 2013-04-26 and no end. Read count to size the series, then iterate data[], skipping buckets where price is null and recording gaps rather than interpolating them. For a volume panel beneath the price line, sum volume only across returned buckets inside each period you label.
To narrow to an event, switch to interval: "1h" for any window after May 9, 2026 and request the hours around it. Requesting 1h for an earlier date does not create intraday detail that Steam never published.
Full request, response, and error reference: POST /v1/archive/steam. Coverage and refresh rates for every endpoint: data coverage. Archive endpoints need a Scale or Enterprise key.