How to build a CS2 skin arbitrage bot using the Skinstrack API
A CS2 arbitrage bot is a price scanner with an alert hook. This guide covers the math, the architecture, and a working bot in JavaScript and Python that pulls cross-market prices from Skinstrack and pings Discord when net ROI clears your threshold.
On this page
What CS2 arbitrage actually is
Cross-market arbitrage means buying a CS2 skin on one marketplace at a lower price and reselling it on another where it is priced higher. The same item can sit at $11.92 on Buff163, $12.50 on CSFloat, and $13.10 on Skinport at the same moment because each marketplace clears its own supply and demand. A bot that watches all of them simultaneously sees the gap.
The catch: fees, trade locks, and listing time eat into that gap. Most apparent opportunities die in the math. The ones that survive are real, and that is what you want to find.
The math, worked out
Take an item priced at $11.92 on Buff163 and $13.10 on Skinport. Gross spread is $1.18, or roughly 9.9 percent. Apply fees:
buy_price = 11.92 # Buff163 ask
sell_price = 13.10 # Skinport ask we list under
skinport_fee = 0.12 # 12% seller fee
proceeds = sell_price * (1 - skinport_fee) # 11.528
net_profit = proceeds - buy_price # -0.392
net_roi = net_profit / buy_price # -3.3%
# Skip. Try sell on Buff163 instead (2.5% fee):
proceeds = 12.50 * (1 - 0.025) # 12.187
net_profit = proceeds - 11.92 # +0.267
net_roi = +2.2% # still thinThe first apparent spread evaporated. The second is real but tight, and probably not worth the listing time on most items. A bot exists to filter for spreads that clear a real threshold, typically 10 percent net ROI or more, on items liquid enough to actually sell.
Architecture
Four boxes. No queue, no database, no machine learning. Add those when you outgrow this.
- 01
Poll prices
Call /paid/items every few minutes to refresh the full marketplace snapshot for your watchlist.
- 02
Score opportunities
For each item, find the cheapest buy market and the highest sell market, then compute net ROI after fees.
- 03
Filter by ROI and liquidity
Drop anything below your minimum ROI or below a liquidity floor. Most candidates die here, and that is the point.
- 04
Alert and execute manually
Push surviving opportunities to Discord, email, or your own dashboard. You buy and sell by hand.
Step 1: pull cross-market prices
One call returns the entire snapshot. Pick the marketplaces you actually plan to use, because more providers means more bytes per response without changing the shape.
curl -H "X-API-KEY: $SKINSTRACK_KEY" \
"https://api.skinstrack.com/v1/paid/items?providers=csfloat,buff,skinport,waxpeer,dmarket&avg=true"Steps 2 and 3: fees, ROI, liquidity
Keep a fee table as data, not as if/else. Compute net ROI per item and gate on both ROI and the Skinstrack liquidity score. A 20 percent spread on an item that trades twice a week is not an opportunity; it is a position you cannot exit.
// Marketplace seller fees (sample, verify on each market).
const SELLER_FEE = {
steam: 0.13, // ~13% combined Steam fee
buff: 0.025, // 2.5%
csfloat: 0.02, // 2%
skinport: 0.12, // 12%
waxpeer: 0.05, // 5%
dmarket: 0.05, // 5%
};
function findArb(item, minRoi = 0.10, minLiquidity = 40) {
if (item.liquidity < minLiquidity) return null;
const buy = item.prices.reduce((a, b) => (a.price < b.price ? a : b));
const sell = item.prices.reduce((a, b) => (a.price > b.price ? a : b));
const sellerFee = SELLER_FEE[sell.provider] ?? 0.10;
const net = sell.price * (1 - sellerFee) - buy.price;
const roi = net / buy.price;
if (roi < minRoi) return null;
return {
name: item.market_hash_name,
buy: { provider: buy.provider, price: buy.price, count: buy.count },
sell: { provider: sell.provider, price: sell.price, count: sell.count },
roi,
liquidity: item.liquidity,
};
}Step 4: push alerts to Discord
Discord webhooks are the path of least resistance. Same shape works for Slack or any generic webhook endpoint. Deduplicate before posting so you do not get the same opportunity 12 times.
async function postToDiscord(webhook, opp) {
const body = {
content:
'**' + opp.name + '**\n' +
'Buy on ' + opp.buy.provider + ' @ $' + opp.buy.price.toFixed(2) + '\n' +
'Sell on ' + opp.sell.provider + ' @ $' + opp.sell.price.toFixed(2) + '\n' +
'Net ROI: ' + (opp.roi * 100).toFixed(1) + '% (liquidity ' + opp.liquidity + ')',
};
await fetch(webhook, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
}Full minimal bot
Around 60 lines, runs in a single process, no dependencies beyond fetch or requests. Drop it on any cheap VPS or Cloudflare Worker and walk away.
// minimal-arb-bot.js - run: node minimal-arb-bot.js
import 'dotenv/config';
const KEY = process.env.SKINSTRACK_KEY;
const WEBHOOK = process.env.DISCORD_WEBHOOK;
const PROVIDERS = ['csfloat', 'buff', 'skinport', 'waxpeer', 'dmarket'];
const MIN_ROI = 0.10;
const MIN_LIQUIDITY = 40;
const POLL_MS = 5 * 60 * 1000;
const SELLER_FEE = {
steam: 0.13, buff: 0.025, csfloat: 0.02,
skinport: 0.12, waxpeer: 0.05, dmarket: 0.05,
};
const seen = new Set();
async function tick() {
const params = new URLSearchParams({ providers: PROVIDERS.join(',') });
const res = await fetch(
'https://api.skinstrack.com/v1/paid/items?' + params,
{ headers: { 'X-API-KEY': KEY } }
);
if (!res.ok) return console.error('fetch failed', res.status);
const items = await res.json();
for (const item of items) {
if (item.liquidity < MIN_LIQUIDITY) continue;
const buy = item.prices.reduce((a, b) => (a.price < b.price ? a : b));
const sell = item.prices.reduce((a, b) => (a.price > b.price ? a : b));
const fee = SELLER_FEE[sell.provider] ?? 0.10;
const roi = (sell.price * (1 - fee) - buy.price) / buy.price;
if (roi < MIN_ROI) continue;
const key = item.slug + ':' + buy.provider + ':' + sell.provider;
if (seen.has(key)) continue;
seen.add(key);
await fetch(WEBHOOK, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
content:
'**' + item.market_hash_name + '**\n' +
'Buy on ' + buy.provider + ' @ $' + buy.price.toFixed(2) +
' -> Sell on ' + sell.provider + ' @ $' + sell.price.toFixed(2) +
' (' + (roi * 100).toFixed(1) + '% net)',
}),
});
}
}
setInterval(tick, POLL_MS);
tick();Risks your bot will not catch
Trade locks and Steam holds
Listing time and slippage
Float and pattern mismatches
Marketplace withdrawal cooldowns
Fee schedule changes
Get the API key your bot needs
Standard tier covers a 5 minute poll across the major marketplaces for a full month.
Questions
Can the Skinstrack API execute trades automatically?
No. The Skinstrack API surfaces prices, volume, liquidity, and metadata across 34+ marketplaces. It does not place buy or sell orders on your behalf. Bots built on top of the API identify opportunities; the user executes the trades manually through each marketplace.
What is CS2 skin arbitrage?
Cross-market arbitrage means buying a CS2 skin on one marketplace at a lower price and reselling it on another where it is priced higher. The spread has to cover marketplace fees, trade hold time, and listing slippage to be worth executing.
How do I avoid false positives?
Filter aggressively. Set a liquidity floor of at least 40 to skip items that rarely trade, require a 10 percent net ROI after fees, and deduplicate alerts by the (item, buy_provider, sell_provider) tuple so you do not re-fire the same opportunity every poll cycle.
Which marketplaces have the lowest fees for arbitrage sells?
CSFloat and Buff163 typically have the lowest seller fees, around 2 to 2.5 percent. Steam and Skinport sit at 12 to 13 percent. Verify the current schedule on each marketplace before locking it into a bot; fees change.
How often should the bot poll?
Every 3 to 10 minutes is a reasonable starting point. Polling more often than that mostly burns your monthly quota for marginal gain, because most arbitrage spreads survive at least a few minutes before they are picked off.
Is CS2 skin arbitrage legal?
Buying and selling CS2 skins on third-party marketplaces is legal in most jurisdictions, but tax treatment varies. Profits from trading digital goods are often taxable. If you are running this at meaningful scale, consult a tax professional in your country.
Keep reading