How to check CS2 skin liquidity and time-to-sell
Check how liquid a CS2 skin is and how long it takes to sell, per market and across markets, one item or thousands at once. With runnable API code.
On this page
A price is only half of what you need to know about a skin. The other half is how long it takes to turn that skin back into money. Two items can both be worth $60 while one sells in an hour and the other sits for three weeks. For inventory valuation, trading bots, and anything that holds positions, time-to-sell is the difference between a price and a realistic exit.
SkinPricer estimates it with a liquidity model built from each market's live listings and its observed sales rate. You get a badge, a median and 90th-percentile time-to-sell in hours, the market where the item clears fastest, and a per-market breakdown. This guide covers the single-item read, the slim summary, batch scoring for up to 100 items, and the Enterprise whole-catalog map.
How the estimate works#
For each supported market the model takes the listings you would have to wait behind and divides by how fast that market has been selling the item over the last day. If you list below the market's current best ask, you go to the front of the queue. If you list at or above it, you wait behind the current supply. The time comes from observed sales velocity only: a crossed book or a pile of bids never manufactures a fast estimate on its own.
The badge is set by the fastest market's median time-to-sell, using fixed thresholds so it stays explainable:
| Badge | Median time-to-sell on the best market |
|---|---|
EXTREMELY_LIQUID | Under 2 hours |
VERY_LIQUID | 2 to 12 hours |
LIQUID | 12 to 48 hours |
ILLIQUID | 2 to 7 days |
VERY_ILLIQUID | 7 to 30 days |
EXTREMELY_ILLIQUID | 30 days or more |
INSUFFICIENT_DATA | Not enough recent sales to support an estimate (older snapshots may say UNTRADED) |
Two safety rails sit on top. When the sales data behind the best market is older than six hours, the badge is capped at VERY_LIQUID, and past a day it is capped at LIQUID, because a "sells within hours" claim needs recent evidence. And confidence (HIGH, MEDIUM, LOW, CRITICAL) plus coverage (full, partial, none) tell you how many markets corroborate the estimate.
Step 1: score one item#
Call GET /v1/liquidity/:marketHashName with the URL-encoded item name. The liquidity endpoints require the Pro plan. Two optional query parameters change the question: askPrice (integer cents) asks "how long if I list at this price?", and market restricts the estimate to one venue.
export SKINPRICER_API_KEY="sp_live_•••" # from your account's API keys page
curl -s "https://pricing.skinpricer.com/v1/liquidity/AK-47%20%7C%20Redline%20(Field-Tested)" \
-H "Authorization: ApiKey $SKINPRICER_API_KEY"// npm i @skinpricer/sdk
import { SkinpricerClient } from "@skinpricer/sdk";
const client = new SkinpricerClient({
apiKey: process.env.SKINPRICER_API_KEY!,
authScheme: "ApiKey",
});
const liq = await client.liquidity.get("AK-47 | Redline (Field-Tested)");
if (liq.medianTimeToSellHours === null) {
// No supported estimate. This is missing evidence, not proof of zero trades.
console.log(`${liq.liquidityBadge}: not enough recent sales to estimate`);
} else {
console.log(
`${liq.liquidityBadge} (${liq.confidence}): median ${liq.medianTimeToSellHours}h,`,
`p90 ${liq.p90TimeToSellHours}h, list on ${liq.bestMarketToList ?? "n/a"}`,
);
}import os
from urllib.parse import quote
import requests
name = "AK-47 | Redline (Field-Tested)"
res = requests.get(
f"https://pricing.skinpricer.com/v1/liquidity/{quote(name, safe='')}",
headers={"Authorization": f"ApiKey {os.environ['SKINPRICER_API_KEY']}"},
timeout=10,
)
res.raise_for_status()
liq = res.json()
print(liq["liquidityBadge"], liq["confidence"], liq["medianTimeToSellHours"])
for m in liq["metrics"]:
print(m["market"], m["salesPerDay"], m["medianTimeToSellHours"]){
"canonicalItemId": "cmlofca920lka01yozajhixt3",
"marketHashName": "Glock-18 | Water Elemental (Factory New)",
"liquidityBadge": "VERY_ILLIQUID",
"confidence": "LOW",
"coverage": "full",
"medianTimeToSellHours": 244.92,
"p90TimeToSellHours": 273.19,
"bestMarketToList": "csfloat",
"salesPerDay": 13,
"activeListings": 132,
"referenceAskUsdCents": 6500,
"marketWideSalesPerDay": 20,
"marketWideListings": 233,
"marketsWithSales": 3,
"marketsWithDepth": 3,
"marketWideMedianTimeToSellHours": 280.4,
"marketWideP90TimeToSellHours": 304.57,
"metrics": [
{
"market": "skinport",
"salesPerDay": 3,
"activeListings": 40,
"bestAskUsdCents": 6516,
"medianTimeToSellHours": 325.34,
"p90TimeToSellHours": 395.1,
"salesDataAgeMinutes": 7,
"listingsDataAgeMinutes": 15
}
],
"notes": [
"no_sales_data:dmarket",
"sales_data_stale:csfloat",
"sales_data_stale:marketcsgo",
"thin_sales:marketcsgo",
"thin_sales:skinport"
],
"calculatedAt": "2026-06-23T22:07:16.813Z"
}notes flags thin or stale inputs per market, and every per-market metric is null when that market has no data.Step 2: read the fields that matter#
medianTimeToSellHours/p90TimeToSellHours: the per-listing estimate on the best market. The p90 is the pessimistic case: nine in ten comparable listings sell within it.bestMarketToList: where the item clears fastest at the reference price. It can benullwhen no market supports an estimate.referenceAskUsdCents: the price the estimate was made against, either youraskPriceor the best ask.marketWide*fields: how long it would take the whole current supply to clear at the combined sales rate. That is a saturation signal, not the time for your one listing.metrics[]: one row per supported market withsalesPerDay,activeListings,bestAskUsdCents, and data ages, so you can see which venues drive the estimate.notes[]: flags such assales_data_stale:<market>orthin_sales:<market>. Surface them next to the badge in your UI.
Step 3: ask "how long at my price?"#
The most practical use of askPrice is pricing a sale. Undercutting the current best ask by one cent moves you to the front of the queue, so the estimate at that price is the fastest exit the market currently supports. Listing above the best ask puts you behind the whole current supply. Comparing both tells you what speed costs.
const name = "AK-47 | Redline (Field-Tested)";
const current = await client.liquidity.get(name);
const ref = current.referenceAskUsdCents;
if (ref !== null) {
const undercut = await client.liquidity.get(name, { askPrice: ref - 1 });
const premium = await client.liquidity.get(name, { askPrice: Math.round(ref * 1.05) });
console.log(`At best ask minus 1c: ${undercut.medianTimeToSellHours ?? "n/a"}h`);
console.log(`At best ask plus 5%: ${premium.medianTimeToSellHours ?? "n/a"}h`);
}Step 4: score many items#
For badges in a list, GET /v1/liquidity/summary/:marketHashName returns just the badge, a lowercase label, a human estimatedSaleTime such as "2 - 6 hours", confidence, and a 0 to 100 score. To score a watchlist or an inventory, POST /v1/liquidity/batch takes up to 100 names in one call and returns the full shape per item, plus a notFound list for names we do not track.
curl -s -X POST "https://pricing.skinpricer.com/v1/liquidity/batch" \
-H "Authorization: ApiKey $SKINPRICER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"marketHashNames": ["AK-47 | Redline (Field-Tested)", "AWP | Asiimov (Field-Tested)"]}'import { chunk } from "@skinpricer/sdk";
const names: string[] = [/* your watchlist */];
for (const batch of chunk(names, 100)) {
const { results, notFound } = await client.liquidity.batch({ marketHashNames: batch });
for (const r of results) console.log(r.marketHashName, r.liquidityBadge);
if (notFound.length > 0) console.warn("Untracked:", notFound);
}On Enterprise, GET /v1/liquidity/items returns the whole catalog as one map keyed by market hash name, recomputed every 10 minutes. Probe GET /v1/liquidity/items/manifest first and compare its version with the copy you hold, or send If-None-Match for a 304, so you only download the full map when it has changed. Both return 503 until the first snapshot is built after a deploy; honor Retry-After.
When to reach for a different endpoint#
Liquidity tells you how fast a listing clears. To know how much size the book can absorb right now, walk the ladder in the order-book depth guide. To turn badges into a realistic portfolio number, combine them with bids in the inventory valuation guide. The liquidity API reference documents every field and error.
Endpoints used in this guide
Plans start at $50/month on the Developer tier. Want to try first? Ask in our Discord for a free 2-day Developer key.
Related guides
- Liquidity & depthIntermediate
How to read CS2 order-book depth across every marketplace
Read aggregated CS2 order-book depth: bid and ask ladders merged across marketplaces, cumulative quantity, and what thin books mean for your fills.
GET /v1/nbbo/depthGET /v1/nbbo6 min read - Build with the APIIntermediate
How to value a CS2 inventory at live market prices
Value a CS2 inventory at live market prices: batch lowest asks and highest buy orders, add liquidity, and report a realistic liquidation value.
GET /v1/aggregations/min-pricesGET /v1/aggregations/max-orders6 min read - Prices & quotesIntermediate
How to estimate what a CS2 skin is really worth
Estimate what a CS2 skin is really worth: combine NBBO, recent history, and a suggested list price with confidence scores instead of one market's number.
GET /v1/recommendations/:marketHashNameGET /v1/nbbo6 min read