# How to get CS2 skin price history at any interval

> Fetch CS2 skin price history in 10-minute, hourly, or daily buckets, across all markets or split per market, ready for charts, alerts, and analysis.

*History & backtesting · Beginner · Updated 2026-09-25*

A current price tells you where an item trades right now. A price history tells you whether that number is normal, how fast it moves, and how the spread between buyers and sellers behaves over time. You need one for every chart, every trend alert, and every model that asks whether a skin is cheap or expensive today.

SkinPricer stores time-bucketed history for every tracked item across every integrated marketplace and serves it from two endpoints: [`GET /v1/pricing/:marketHashName/history`](/api-docs/history) for one blended cross-market series, and [`GET /v1/pricing/:marketHashName/history/markets`](/api-docs/history-markets) for one series per venue. This guide covers both, how bucket sizes are chosen, and the handful of fields that are easy to misread.

## What a history bucket contains

The response is a list of buckets, oldest first. Each bucket summarizes everything observed in one time window. Every price is an integer number of USD cents, so `3522` means $35.22.

| Field | Meaning |
| --- | --- |
| `timestamp` | The start of the bucket, in UTC. The data describes the window that begins here. |
| `avgPrice` / `medianPrice` | Mean and median of the prices observed in the window. |
| `minPrice` / `maxPrice` | The cheapest and most expensive observation. The maximum can include extreme listings. |
| `bestAskCents` / `bestBidCents` | The best ask and best bid in the window, `null` when a side had no quotes. |
| `spreadBps` | The bucket's bid/ask spread in basis points, using the mid price as the denominator. |
| `listingCount` | How many listings were observed in the window. |
| `totalQuantity` / `volume` | Buy-order quantity (buy-order history only) and rolling sale count (sale history only). `null` otherwise. |

## Step 1: request a series

Put the item's exact `market_hash_name` in the path, URL-encoded, and send your API key. History reads require the Pro plan or higher, because they are served by the `historical_data` feature. With no query parameters you get the last 30 days of sell-side (ask) history at a bucket size chosen for you.

**curl**

```bash
export SKINPRICER_API_KEY="sp_live_•••"   # from your account's API keys page

curl -s "https://pricing.skinpricer.com/v1/pricing/AK-47%20%7C%20Redline%20(Field-Tested)/history?interval=DAY_1&from=2026-06-27T00:00:00Z" \
  -H "Authorization: ApiKey $SKINPRICER_API_KEY"
```

**Node (SDK)**

```ts
// npm i @skinpricer/sdk
import { SkinpricerClient, formatUsd } from "@skinpricer/sdk";

const client = new SkinpricerClient({
  apiKey: process.env.SKINPRICER_API_KEY!,
  authScheme: "ApiKey",
});

const ninetyDaysAgo = new Date(Date.now() - 90 * 24 * 60 * 60 * 1000);

const series = await client.history.get("AK-47 | Redline (Field-Tested)", {
  from: ninetyDaysAgo,
  interval: "DAY_1",
});

for (const bucket of series.history) {
  const ask = bucket.bestAskCents;
  console.log(bucket.timestamp.slice(0, 10), ask === null ? "no asks" : formatUsd(ask));
}
```

**Python**

```python
import os
from datetime import datetime, timedelta, timezone
from urllib.parse import quote

import requests

name = "AK-47 | Redline (Field-Tested)"
since = datetime.now(timezone.utc) - timedelta(days=90)

res = requests.get(
    f"https://pricing.skinpricer.com/v1/pricing/{quote(name, safe='')}/history",
    params={"interval": "DAY_1", "from": since.isoformat()},
    headers={"Authorization": f"ApiKey {os.environ['SKINPRICER_API_KEY']}"},
    timeout=10,
)
res.raise_for_status()
data = res.json()

print("bucket size served:", data.get("effectiveInterval"))
for bucket in data["history"]:
    ask = bucket["bestAskCents"]
    print(bucket["timestamp"][:10], "no asks" if ask is None else f"${ask / 100:.2f}")
```

*The item name is a path segment, so encode it (spaces, the pipe, and parentheses). The SDK encodes for you.*

**Response 200** (`GET /v1/pricing/:marketHashName/history`)

```json
{
  "canonicalItemId": "cmlofca920lka01yozajhixt3",
  "name": "Glock-18 | Water Elemental (Factory New)",
  "listingType": "SELL_OFFER",
  "salesWindow": null,
  "effectiveInterval": "1d",
  "history": [
    {
      "timestamp": "2026-05-25T00:00:00.000Z",
      "avgPrice": 4923,
      "medianPrice": 4222,
      "minPrice": 3522,
      "maxPrice": 8805,
      "listingCount": 1442,
      "totalQuantity": null,
      "volume": null,
      "bestBidCents": null,
      "bestAskCents": 3522,
      "spreadBps": null
    }
  ]
}
```

*The documented 200 response. This bucket had asks but no bids, so the bid, the spread, and the bid-side counts are null rather than zero.*

## Step 2: choose the bucket size

History is stored in three rollups: 10-minute, hourly, and daily. If you do not ask for a size, the API picks one from the length of your `from` to `to` window. If you pass `interval`, it is snapped to the nearest stored rollup, and the response echoes the size actually served in `effectiveInterval`.

| You ask for | You get |
| --- | --- |
| No `interval`, window up to 7 days | 10-minute buckets |
| No `interval`, window up to 180 days | Hourly buckets |
| No `interval`, window longer than 180 days | Daily buckets |
| `MINUTE_5` or `MINUTE_15` | 10-minute buckets |
| `HOUR_1` or `HOUR_6` | Hourly buckets |
| `DAY_1` | Daily buckets |

> **Stay under the point cap:** One request can return at most 10,000 points. A year of hourly data is about 8,760 buckets and fits; a year of 10-minute data does not and returns a 400 asking you to narrow the range. For long windows, use `DAY_1` or split the range into pages of your own.

## Step 3: pick what you are charting

The `listingType` parameter decides which side of the market the buckets describe. The default is the ask side, which is what most price charts show.

- `SELL_OFFER` (default): listings for sale. `bestAskCents` is the cheapest ask in the window, and the price statistics describe observed asks.
- `BUY_ORDER`: standing buy orders. `totalQuantity` reports how many units buyers were bidding for.
- `SALE_HISTORY`: completed sales as reported by the markets that publish them, paired with `salesWindow` (`24h`, `7d`, `30d`, or `90d`, default `30d`). `volume` is the number of sales in that trailing window.

> **Sale volume is a rolling window:** On `SALE_HISTORY`, each bucket reports the sales in the trailing `salesWindow` as of that bucket, not the sales that happened inside the bucket. Adjacent buckets overlap, so adding them up counts the same sales many times. Read one bucket, or take the difference between buckets spaced a full window apart.

## Step 4: split the series per market

The aggregate series answers "what did this item cost across the market". When you need to know where it was cheapest, or how one venue trades against another, call the per-market variant. It takes the same parameters plus an optional `markets` filter and returns a `series` array with one `history` per venue.

```ts
const byMarket = await client.history.byMarket("AK-47 | Redline (Field-Tested)", {
  markets: ["buff163", "csfloat", "steamcommunity"],
  interval: "HOUR_1",
});

for (const { market, history } of byMarket.series) {
  const last = history.at(-1);
  console.log(market, last?.bestAskCents ?? "no asks", "cents");
}
```

*Filtering to the venues you care about keeps responses small and helps long windows stay under the point cap, which counts one series per market.*

**Response 200** (`GET /v1/pricing/:marketHashName/history/markets`)

```json
{
  "canonicalItemId": "cmlofczf90qj401vsd80dskd1",
  "name": "AK-47 | Redline (Field-Tested)",
  "listingType": "SELL_OFFER",
  "salesWindow": null,
  "effectiveInterval": "1d",
  "series": [
    {
      "market": "buff163",
      "history": [
        {
          "timestamp": "2026-09-22T00:00:00.000Z",
          "avgPrice": 3343,
          "medianPrice": 2942,
          "minPrice": 2532,
          "maxPrice": 5565,
          "listingCount": 8472,
          "totalQuantity": null,
          "volume": null,
          "bestBidCents": 2584,
          "bestAskCents": 2532,
          "spreadBps": -203
        }
      ]
    },
    {
      "market": "csfloat",
      "history": [
        {
          "timestamp": "2026-09-22T00:00:00.000Z",
          "avgPrice": 2524,
          "medianPrice": 2526,
          "minPrice": 2495,
          "maxPrice": 2526,
          "listingCount": 3655,
          "totalQuantity": null,
          "volume": null,
          "bestBidCents": 2500,
          "bestAskCents": 2495,
          "spreadBps": -20
        }
      ]
    }
  ]
}
```

## Reading the numbers correctly

- `null` means no data, not zero. A bucket with no bids has `bestBidCents: null`; averaging it in as 0 drags every metric down.
- In the aggregate series, `bestBidCents` is the best cash-executable bid that does not exceed the bucket's best ask, so the blended spread is never negative. Per-market series report each venue's own quotes, and their spreads can be negative.
- History `spreadBps` divides by the mid price. The live [NBBO](/api-docs/nbbo) divides by the bid, so do not compare the two numbers directly.
- `maxPrice` on ask history includes whatever the most optimistic seller listed. It is not a fair-value estimate; chart `bestAskCents` or `medianPrice` instead.
- Missing buckets mean nothing was observed in that window. Keep the gap in your chart rather than drawing a line straight through it.

## When to reach for a different endpoint

For the price right now, use the [best bid and ask](/resources/cs2-best-bid-and-ask) or the full [live pricing snapshot](/resources/live-cs2-skin-prices). For float-range or Doppler-phase history, the item-level series is too coarse; read [attribute price history](/api-docs/attribute-history) instead, as shown in the [float range guide](/resources/cs2-float-range-prices). And if you are about to test a trading rule on this data, read the [backtesting guide](/resources/backtest-cs2-trading-strategy) first: it covers point-in-time handling that a chart can safely ignore but a strategy cannot.

## Endpoints used

- [GET /v1/pricing/:marketHashName/history](/api-docs/history.md)
- [GET /v1/pricing/:marketHashName/history/markets](/api-docs/history-markets.md)

## Related guides

- [How to backtest a CS2 skin trading strategy](/resources/backtest-cs2-trading-strategy.md)
- [How to get BUFF163 prices, float buckets, and history in USD](/resources/buff163-prices-and-history.md)
- [How to get Steam Community Market prices without scraping](/resources/steam-market-prices-without-scraping.md)
- [How to estimate what a CS2 skin is really worth](/resources/cs2-skin-fair-value.md)
