Platform inventory tutorial

Create a CS2 Inventory Value Report

Build an HTML report that shows a public CS2 inventory’s value by marketplace and identifies items without a current price.

Last updated 2026-09-04 · Reviewed by OpenSkin · 30 minutes · Intermediate

What you will build

Build a Node.js script that reads one public CS2 inventory and creates an HTML report with a separate total for each marketplace.

Completed CS2 inventory value report with marketplace totals and unit counts
The report shows how many units contribute to each marketplace total.

Before you start

  • Node.js 20 or newer
  • A Platform API key with inventory:read
  • A SteamID64 with a public CS2 inventory

Project files

inventory-report/
  inventory-report.mjs
  report.html  (generated)

Build it

  1. Set the API key and SteamID64

    Keep the API key in an environment variable. Pass the 17-digit SteamID64 when you run the script.

  2. Fetch the public inventory

    The script stops with the API error when the inventory is private, the key is invalid, or Steam is unavailable.

  3. Keep marketplace totals separate

    The response includes a valuation.markets list. It contains one total per marketplace, plus counts of items with and without a price.

  4. List items without any price

    Items with no price from any marketplace remain visible in the report instead of being counted as $0.

  5. Open the generated report

    Run the command below, then open report.html in a browser.

inventory-report.mjs

Fetch, validate, summarize, and write the report

import {writeFile} from 'node:fs/promises'
const API = process.env.OPENSKIN_BASE_URL ?? 'https://api-v2.openskin.dev'
const key = process.env.OPENSKIN_API_KEY
const steamId = process.argv[2]
if (!key) throw new Error('Set OPENSKIN_API_KEY before running the report.')
if (!/^\d{17}$/.test(steamId ?? '')) throw new Error('Pass a 17-digit SteamID64.')

const response = await fetch(`${API}/v2/inventories/${steamId}`, {headers:{'X-API-Key':key}})
const payload = await response.json()
if (!response.ok) throw new Error(`${response.status}: ${payload.error?.message ?? 'Inventory request failed'}`)
const inventory = payload.data
const markets = inventory.valuation?.markets ?? []
const unpriced = inventory.assets.filter(asset => (asset.prices?.length ?? 0) === 0)

const escape = value => String(value).replace(/[&<>"]/g, char => ({"&":"&amp;","<":"&lt;",">":"&gt;","\"":"&quot;"}[char]))
const money = (value, currency) => new Intl.NumberFormat('en-US',{style:'currency',currency}).format(value)
const marketplaceNames = {steam:'Steam', skinport:'Skinport', buff:'Buff163', youpin:'YouPin', csfloat:'CSFloat'}
const checked = new Date(inventory.observed_at).toLocaleString('en-US',{timeZone:'UTC',dateStyle:'medium',timeStyle:'short'}) + ' UTC'
const marketRows = markets.map(market => `<tr><th>${escape(marketplaceNames[market.marketplace] ?? market.marketplace)}</th><td>${money(market.total, market.currency)}</td><td>${market.priced_assets}</td><td>${market.unpriced_assets}</td></tr>`).join('')
const unpricedRows = unpriced.slice(0,20).map(asset => `<li>${escape(asset.item?.market_hash_name ?? asset.asset_id)}</li>`).join('')

const html = `<!doctype html><meta charset="utf-8"><title>CS2 inventory value report</title><style>*{box-sizing:border-box}body{font:16px system-ui;margin:0;background:#f5f6f4;color:#202320}main{width:min(760px,calc(100% - 40px));margin:auto;padding:54px 0}.eyebrow{color:#52745b;font-size:12px;font-weight:800;letter-spacing:.13em;text-transform:uppercase}h1{font-size:clamp(38px,7vw,50px);margin:10px 0}table{width:100%;background:white;border:1px solid #d9ddd9;border-collapse:collapse}th,td{padding:14px 16px;border-bottom:1px solid #d9ddd9;text-align:left}td{text-align:right}ul{background:white;border:1px solid #d9ddd9;padding:12px 18px 12px 42px}</style><main><div class="eyebrow">Inventory value report</div><h1>CS2 inventory value report</h1><p>${inventory.count} items. Inventory checked ${escape(checked)}.</p><h2>Value by marketplace</h2><table><thead><tr><th>Marketplace</th><th>Total</th><th>Units priced</th><th>Units without a price</th></tr></thead><tbody>${marketRows || "<tr><td colspan=4>No marketplace totals are available.</td></tr>"}</tbody></table><h2>No price from any marketplace (${unpriced.length})</h2><ul>${unpricedRows || "<li>None</li>"}</ul></main>`
await writeFile('report.html', html)
console.log(`Wrote report.html for ${inventory.count} items and ${markets.length} marketplaces`)

Run it

export OPENSKIN_API_KEY='replace-with-your-key'
node inventory-report.mjs 76561198000000000

Open report.html in your browser.

Expected output

Wrote report.html for 40 items and 2 marketplaces

Marketplace   Total       Units priced   Units without a price
Steam         $4,281.16   38             2
CSFloat       $3,944.80   36             4

How it works

  • Each marketplace gets its own total because the same item can have a different price on each market.
  • The priced and unpriced counts are item quantities for price records returned by that marketplace. They may not add up to the full inventory when that marketplace returned no record for some items.
  • The inventory checked time comes from the API. It is not the time when you opened report.html.

Verify the result

Open report.html and confirm that the number of items matches the inventory count returned by the API. Each marketplace row must show its own total plus counts of items with and without a price.

Troubleshooting

What you seeLikely causeWhat to do
401 or 403The API key is missing, expired, or does not include inventory access.Create a key with inventory:read and pass it as OPENSKIN_API_KEY.
Inventory unavailableThe Steam inventory is private or Steam did not return it.Test with the 17-digit Steam ID for an account whose inventory you can view while signed out.
No marketplace totals are availableThe inventory returned no current marketplace prices.Show the items without a price. Do not substitute a $0 total.