Skip to main content

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.

Updated 6 min readBeginnercurl · TypeScript · PythonMarkdown
On this page
  1. What a history bucket contains
  2. Step 1: request a series
  3. Step 2: choose the bucket size
  4. Step 3: pick what you are charting
  5. Step 4: split the series per market
  6. Reading the numbers correctly
  7. When to reach for a different endpoint

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 for one blended cross-market series, and GET /v1/pricing/:marketHashName/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.

FieldMeaning
timestampThe start of the bucket, in UTC. The data describes the window that begins here.
avgPrice / medianPriceMean and median of the prices observed in the window.
minPrice / maxPriceThe cheapest and most expensive observation. The maximum can include extreme listings.
bestAskCents / bestBidCentsThe best ask and best bid in the window, null when a side had no quotes.
spreadBpsThe bucket's bid/ask spread in basis points, using the mid price as the denominator.
listingCountHow many listings were observed in the window.
totalQuantity / volumeBuy-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.

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"
// 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));
}
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 200GET /v1/pricing/:marketHashName/history
Field reference
{
  "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.
Live
Try it live

Pull the last 30 days of AK-47 | Redline (Field-Tested) history right now, in your browser.

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 forYou get
No interval, window up to 7 days10-minute buckets
No interval, window up to 180 daysHourly buckets
No interval, window longer than 180 daysDaily buckets
MINUTE_5 or MINUTE_1510-minute buckets
HOUR_1 or HOUR_6Hourly buckets
DAY_1Daily buckets

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.

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.

Node (SDK)
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 200GET /v1/pricing/:marketHashName/history/markets
Field reference
{
  "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 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 or the full live pricing snapshot. For float-range or Doppler-phase history, the item-level series is too coarse; read attribute price history instead, as shown in the float range guide. And if you are about to test a trading rule on this data, read the backtesting guide first: it covers point-in-time handling that a chart can safely ignore but a strategy cannot.

Endpoints used in this guide

Run this guide against live data

Plans start at $50/month on the Developer tier. Want to try first? Ask in our Discord for a free 2-day Developer key.