Steam inventory value
Steam inventory API with item values
GET /v1/inventory returns a typed inventory result for one SteamID64 and game. The response separates item count, value, trade state, missing prices and cache freshness.
GET /v1/inventoryBearer authenticationUpdated
WOK APIcurl --fail-with-body \
-H "Authorization: Bearer $WOK_API_KEY" \
"https://woksteamapi.com/v1/inventory?steam_id=76561198090744629&game=cs2&top=0"Quickstart
Use WOK from a trusted server. Keep the API key outside browser bundles and public repositories.
Create a WOK account and API key, set WOK_API_KEY on your server, then replace the example SteamID64 with a public inventory. The Python example uses python -m pip install requests; the JavaScript example uses Node.js with built-in fetch. Which Steam API key do I need?
top=0 returns all grouped item rows, not just the ten most valuable. Read status even after HTTP 200. No Inspect add-on is needed for these examples. Check the free and paid plan limits before choosing a polling interval.
WOK APIcurl --fail-with-body \
-H "Authorization: Bearer $WOK_API_KEY" \
"https://woksteamapi.com/v1/inventory?steam_id=76561198090744629&game=cs2&top=0"WOK API// Node.js: keep the API key on your server.
const url = new URL("https://woksteamapi.com/v1/inventory");
url.search = new URLSearchParams({
steam_id: "76561198090744629",
game: "cs2",
top: "0"
});
const response = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.WOK_API_KEY}` }
});
if (!response.ok) throw new Error(`${response.status}: ${await response.text()}`);
const inventory = await response.json();
if (!["ok", "empty"].includes(inventory.status)) {
throw new Error(`Inventory unavailable: ${inventory.status}`);
}
console.log({ totalUSD: inventory.total, unpriced: inventory.unpriced });WOK APIimport os
import requests
response = requests.get(
"https://woksteamapi.com/v1/inventory",
headers={"Authorization": f"Bearer {os.environ['WOK_API_KEY']}"},
params={
"steam_id": "76561198090744629",
"game": "cs2",
"top": 0,
},
timeout=30,
)
response.raise_for_status()
inventory = response.json()
if inventory["status"] not in ("ok", "empty"):
raise RuntimeError(f"Inventory unavailable: {inventory['status']}")
print({"total_usd": inventory["total"], "unpriced": inventory["unpriced"]})Choose what your inventory value means
A public inventory tells you what a player owns. The chosen pricing mode determines which catalog observations value those items.
| Request choice | Selection rule | What to check |
|---|---|---|
price_source=auto | Prefer backpacktf, steam_avg, steam, steam_market, skinport, then tm when available after price validation. | Explicit auto overrides a key's saved source. It is not the average or lowest quote; BUFF and instant-sell buy orders are excluded. |
price_source=steam&strict=1 | Use the Steam family: steam_avg, steam, then steam_market. | Strict disables other-family fallback; it does not force one exact Steam feed. |
price_source=buff&strict=1 | Use a matched BUFF163 CS2 price only. | Unmatched items stay null and contribute to unpriced. With strict=0, other market sources may fill gaps. |
price_source=skins&strict=1 | Use a matched Skins.com CS2 lowest listing only. | Unmatched items stay null and contribute to unpriced. Check priceupdatedat on every priced item. |
price_source=waxpeer&strict=1 | Use a matched Waxpeer lowest listing for CS2, Dota 2, Rust or TF2. | Missing listings stay unpriced. WOK saves its own USD observation time. |
price_source=49skins&strict=1 | Use a matched 49Skins CS2 listing converted from EUR to USD. | Only currently listed items appear; marketplace checkout fees are not included. |
price_source=csdeals&strict=1 | Use a matched CS.DEALS CS2 lowest listing when its feed is configured. | Missing or unavailable provider observations stay unpriced. |
price_source=haloskins&strict=1 | Use a matched HaloSkins CS2 lowest in-stock listing when its feed is configured. | Only P2P or bot listings with stock are considered. |
price_source=mix | Use the lowest accepted market observation, including fresh third-party listing feeds. | Stale third-party observations and grossly underpriced outliers are excluded; steam_buy remains separate. A listing floor is not a realized sale price. |
Always read status, unpriced, items[].pricesource and items[].priceupdatedat. For a priced item, pricemedian × count is its grouped value; volume is market metadata, not inventory quantity. Keep total, untradable_value and tradelocked_value separate rather than adding them into an unlabeled estimate.
Normal OK/EMPTY cache hits reapply current local prices while keeping the existing inventory snapshot and TTL. Changing price_source does not request a provider refresh. A recent inventory scan can still have an older price observation; show those two ages separately.
Quote items without a SteamIDRefresh only cached inventory pricesCompare WOK with SteamWebAPIValidate your existing integration
Which Steam inventory API do you need?
Three similarly named interfaces solve different jobs. Choose by ownership, input and required output.
| Interface | Designed for | What you receive |
|---|---|---|
| WOK Steam Inventory API | Reading a public community inventory by SteamID64 | Normalized items, USD values, trade state and cache metadata |
| Raw Steam Community inventory | Building and operating your own public-inventory integration | Assets and descriptions that your application must paginate, join and price |
| Steamworks IInventoryService | A publisher managing its own game's Steam Inventory Service | Publisher economy inventory; GetInventory requires a publisher key with Economy permissions |
WOK is independent of Valve. The official Steamworks IInventoryService reference describes the publisher interface; it is not a general-purpose endpoint for reading another game's public inventory.
Quote individual Steam item pricesValue a complete inventoryBatch inventory requestsResolve Steam profiles
Supported games, AppIDs and price coverage
The game key chooses the inventory. Available price sources are a separate capability; a successful inventory read does not guarantee a price for every item.
| Game guide | game parameter | Steam AppID | Pricing boundary |
|---|---|---|---|
| CS2 inventory API | cs2 | 730 | Multiple catalog sources; availability varies by item |
| DOTA 2 inventory API | dota2 | 570 | Multiple catalog sources; availability varies by item |
| RUST inventory API | rust | 252490 | Multiple catalog sources; availability varies by item |
| TF2 inventory API | tf2 | 440 | Multiple catalog sources; availability varies by item |
| PUBG inventory API | pubg | 578080 | Steam Market observations; coverage may be incomplete |
| s&box inventory API | sandbox | 590830 | Steam Market observations; coverage may be incomplete |
| Unturned inventory API | unturned | 304930 | Steam Market observations; coverage may be incomplete |
| PAYDAY 2 inventory API | payday2 | 218620 | Steam Market observations; coverage may be incomplete |
For Steam trading cards and Community inventory, use game=753. This is an inventory-only target; card prices are not included. Other numeric AppIDs use the generic inventory contract.
Keep items with pricemedian=null visible and show the unpriced count alongside the total. For cached inventories, refresh the prices without another inventory scan; check cache age separately from price update time.
Track visible inventory changes with opt-in snapshots
After a normal public inventory request, capture its complete, still-valid local cache with the same WOK key. This call makes no new Steam or proxy request. It does not schedule background scans.
WOK APIcurl --fail-with-body -X POST \
-H "Authorization: Bearer $WOK_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"steam_id\":\"$STEAM_ID\",\"game\":\"cs2\"}" \
"https://woksteamapi.com/v1/inventory-snapshots"The first response is a baseline with no asserted additions. On a later distinct observation, diff.added and diff.removed report changes in quantities by exact market name. Read from_observed_at and to_observed_at as the observation window. A change does not identify an asset, trade, transfer or exact event time.
Use GET /v1/inventory-snapshots?steam_id=...&game=cs2 to list this key's observations, GET /v1/inventory-snapshots/{snapshot_id}/diff for paginated changes, and DELETE /v1/inventory-snapshots?steam_id=...&game=cs2 to erase them. History is per key and retained for at most 30 days. Private status purges it. The integration guide covers response fields and failure states.
Response example
The values below illustrate the response shape. Field names and types match the public contract.
JSON
application/json{
"steamid": "76561198090744629",
"game": "cs2",
"status": "ok",
"total": 24.68,
"items_total": 2,
"sellable": 2,
"untradable": 0,
"untradable_value": 0.0,
"tradelocked": 0,
"tradelocked_value": 0.0,
"unpriced": 0,
"items": [
{
"markethashname": "Example market item",
"count": 2,
"game": "cs2",
"tradable": 1,
"marketable": 1,
"pricemedian": 12.34,
"prices": {
"steam": 12.34
},
"pricesource": "steam",
"priceupdatedat": 1787572800.0,
"volume": 3
}
],
"meta": {
"cached": true,
"shared": false,
"lane": "cache",
"saved": false,
"ms": 0,
"bytes": 0,
"paid_bytes": 0,
"upstream_attempts": 0,
"detail": "",
"cache_age": 42,
"cache_ttl": 1800,
"price_source": "steam"
}
}
Response schema
Stable fields for typed clients, storage and error handling.
| Field | Type | Meaning |
|---|---|---|
steamid | string | Canonical 17-digit SteamID64. |
game | string | Documented key: cs2, rust, tf2, dota2, pubg, sandbox, unturned or payday2. Numeric Steam appids are also accepted and returned as app<appid> without catalog pricing. |
status | string | ok, empty, private, notfound, limited or error. |
total | number | Priced sellable inventory total in USD. |
items_total | integer | Number of item instances before top truncation. |
sellable | integer | Tradable and marketable item instances, including unpriced ones. |
untradable | integer | Item instances excluded from sellable value. |
untradable_value | number | Value of excluded untradable items in USD. |
tradelocked | integer | Item instances currently trade locked. |
tradelocked_value | number | Value of trade-locked items in USD. |
unpriced | integer | Instances without a usable price. |
items[] | array | Items with market names, count, trade state, prices and optional inspect links. |
items[].markethashname | string | Canonical price-catalog market hash name. |
items[].marketname | string | Steam market display name when supplied. |
items[].type | string | Steam-supplied item type text; not a WOK-normalized category. |
items[].count | integer | Grouped item instance count. |
items[].tradable | integer | Steam tradability flag, 0 or 1. |
items[].marketable | integer | Steam marketability flag, 0 or 1. |
items[].tradelocked | boolean | Whether the grouped item is trade locked. |
items[].tradelockuntil | number|null | Unix expiry when a trade lock is known. |
items[].pricemedian | number|null | Selected source median price in USD. |
items[].pricesource | string | Selected catalog source; empty when the item is unpriced. |
items[].priceupdatedat | number|null | Selected source observation time, separate from inventory cache age. |
items[].prices | object | Per-source USD prices. |
items[].inspect_links | array | Optional CS2 inspect actions. |
meta | object | Cache age/TTL, cached/shared flags, timing, bytes, lane and upstream-attempt metadata. |
meta.cached | boolean | Whether the inventory snapshot was reused. |
meta.shared | boolean | Whether this request joined an in-flight singleflight fetch. |
meta.cache_age | integer | Snapshot age in seconds. |
meta.cache_ttl | integer | Applied freshness window in seconds. |
items[].volume | integer | External market activity metadata; never inventory quantity. |
Limits and error behavior
Rules clients should handle explicitly in production.
The Steam inventory must be public. Private, missing and empty inventories are distinct statuses, not successful empty data.
top accepts 0 through 100. top=0 returns every item row; items_total still describes the complete inventory when a top limit is used.
Prices are USD. An item can be valid while pricemedian is null; use unpriced instead of treating missing prices as zero-value evidence.
A partial or malformed Steam inventory is not saved as a successful snapshot. On GET /v1/inventory, validation/auth failures use HTTP 400/401/402/404/422/429; Steam outcomes private, notfound, limited and error are returned with HTTP 200 in response.status. The compatibility GET /steam/api/inventory maps them to 403/404/429/502 (empty to 410).
Cache and freshness semantics
How to tell whether data was reused, shared or refreshed.
Successful inventory snapshots use a 1,800-second default TTL. Negative defaults are 21,600 seconds for empty, 3,600 seconds for private and 600 seconds for not found.
For authenticated inventory responses that reach validation/result handling, read X-Wok-Cache-Age and X-Wok-Cache-TTL when present; authentication and quota errors may omit them.
Identical concurrent misses are coalesced. A waiter receives X-Wok-Singleflight: 1 and does not start another Steam inventory fetch.
Use refresh_prices=1 or POST /v1/inventory/refresh-prices only with an active compatible OK/EMPTY snapshot. Repricing never fetches Steam or resets inventory age/TTL; 404 inventory_cache_miss means run a normal inventory scan. refresh_prices=1 cannot be combined with no_cache=1; prices_updated_at identifies the repricing operation.
A normal cache hit for an OK/EMPTY inventory also reapplies the current local price catalog before returning its value. Inventory snapshot age and item price age are separate: repricing does not fetch Steam, force a provider refresh or extend the inventory TTL.
Set price_source=auto explicitly to override a key's saved source. Auto uses an ordered source preference, not an average or the lowest price. Read items[].pricesource and items[].priceupdatedat for the selected observation; source availability varies by game and item.
Frequently asked questions
Direct answers about access, freshness and result semantics.
How do I get a complete Steam inventory as JSON?
Use GET /v1/inventory with steam_id, game and top=0. The native response contains all grouped rows in items. A top limit shortens the item list, not the inventory totals.
Does the API work with private inventories?
No. Private, missing and empty inventories are returned as distinct states so clients do not mistake unavailable data for an empty public inventory.
How fresh is a successful inventory response?
The default positive cache TTL is 1,800 seconds. X-Wok-Cache-Age and X-Wok-Cache-TTL describe the server-side snapshot when available.
What happens when identical requests arrive together?
Singleflight coalesces concurrent misses for the same SteamID and game, so only one request starts the upstream inventory fetch in that API process.
Handle inventory states before showing a value
GET /v1/inventory has two result layers: HTTP authentication and quota errors, then the Steam inventory status inside a successful HTTP response.
| Result | Application behavior |
|---|---|
HTTP 200: ok | Display items and total together with unpriced coverage and snapshot age. |
HTTP 200: empty | Show an empty public inventory, not a failed lookup. |
HTTP 200: private or notfound | Show an unavailable inventory. Do not store its total as a real zero or retry it in a tight loop. |
HTTP 200: limited or error | Keep the last successful value visibly dated, or show unavailable. Retry temporary failures with a bounded delay. |
| HTTP 401 / 402 / 422 | Check the response error and fix the key, access/plan or parameters. Repeating an unchanged request is not a recovery strategy. |
| HTTP 429 | Check the returned error, quota and Retry-After when present. Apply backoff instead of adding parallel retries. |
For production polling, read GET /v1/key for the key's effective quota and reset times; that lookup does not consume quota. A successful inventory cache hit still consumes a request unit. Group a roster with batch inventory requests, remembering that quota is counted per unique player-game pair, not once per batch.
Use price-only refresh when you only need to revalue an active cached snapshot. It does not prove the player still owns the items. Before an ownership-sensitive action, decide whether the reported inventory age is acceptable; no_cache=1 requests a fresh scan but cannot bypass Steam privacy or availability.
Detailed retry and rate-limit handlingCompatibility endpoint status mappingIntegration support