Loading…

Inventory

Valuation, performance and collections for any public Steam inventory, and purchase records and stack changes for your own.

Items in a profile inventory, with market value

GET/api/inventory/{profileId}

One row per item held, with its quantity and current market value, paginated. Accepts a profile id or guid, a Steam vanity name, or a 64-bit Steam id.

The default page size is large (2,000) because this is the whole-inventory view, and pageSize is clamped to 5,000 rather than rejected. metric chooses what the primary sort ranks on and priceSort gives it a direction.

It answers 401 when the profile has opted out of item analytics and the caller is neither its owner nor an administrator. That applies to every per-profile inventory read.

Parameters

NameInRequiredDescription
profileIdpathyesProfile id, profile guid, vanity url, or 64-bit Steam id.
searchquerynoFilter by item name or type.
collectionquerynoFilter to a single collection name (all = no filter).
priceSortquerynoPrimary sort by per-item market price. Defaults to desc.

One of: asc, desc

nameSortquerynoName order (A-Z / Z-A). Defaults to az.

One of: az, za

sortKeyquerynoWhich dropdown is the primary sort; the other only breaks ties. Defaults to price.

One of: price, name

metricquerynoWhat the primary sort ranks on (priceSort gives it a direction). price = per-unit market price, stack = unit price × owned quantity, ath/atl = the current Steam ask as a ratio of the all-time high/low sale (ath desc / atl asc = "closest to"), demand = Steam units sold in the last 24 h, supply = Steam sell-order count. Defaults to price; an unrecognised value falls back to it rather than rejecting. ath, atl, demand and supply are Steam-only measures, so unlike price and stack they ignore the market parameter and always rank on Steam’s figures.

One of: price, stack, ath, atl, demand, supply

pagequeryno1-based page index. Defaults to 1.
pageSizequerynoItems per page. Defaults to 2000, max 5000.
marketquerynoValuation source for each item price: steam (default) or a specific market key (e.g. Skinport) present in this inventory. Also scopes visibility: unless includeUnpriced=true, only items priced by the selected view are listed.
includeUnpricedquerynotrue includes every owned item. Default (false) hides items the selected view cannot price — Steam view requires a resale price (store-only items are hidden), a market view requires that market's price. The inventory value endpoint keeps counting the full owned set.

One of: true, false

currencyqueryno3-letter currency code. Defaults to the profile FK / USD.

Request

curl "https://api.scmm.app/api/inventory/<profileId>?search=<search>"

Response 200 · application/json · object

  • array<object>
  • page*number
  • pageSize*number
  • total*number
  • hasMore*boolean
  • collections*array<string>
  • markets*array<string>

Collections represented in a profile inventory

GET/api/inventory/{profileId}/collections

One row per collection the profile owns at least one item from, with how much of the set it holds and what that is worth. It is the set-completion view of an inventory rather than a list of items.

It answers 401 when the profile has opted out of item analytics and the caller is neither its owner nor an administrator.

Parameters

NameInRequiredDescription
profileIdpathyesProfile id, profile guid, vanity url, or 64-bit Steam id.
includeUnownedquerynoIf true, returns every tracked collection regardless of ownership (development env only).
currencyqueryno3-letter currency code. Defaults to the profile FK / USD.

Request

curl "https://api.scmm.app/api/inventory/<profileId>/collections?includeUnowned=false"

Response 200 · application/json · array<object>

  • name*string
  • totalItems*number
  • ownedItems*number
  • completionPercent*number

    Share of the collection's items the profile owns, from 0 to 100.

  • totalCost*number | null

    Cost of buying every item in the collection at current prices, in hundredths of currency, or null when it cannot be priced.

  • ownedValue*number

    Value of the items the profile owns, in hundredths of currency.

  • currency*string
  • array<object>

Total inventory value over a trailing window

GET/api/inventory/{profileId}/history

One point per day of the profile’s total inventory value, oldest first. A day the value was never computed on is absent rather than zero — never interpolate across a gap, because a missing day means no snapshot, not a worthless inventory.

It answers 401 when the profile has opted out of item analytics and the caller is neither its owner nor an administrator.

Parameters

NameInRequiredDescription
profileIdpathyesProfile id, profile guid, vanity url, or 64-bit Steam id.
periodquerynoWindow: 1d, 7d, 30d, 90d. Defaults to 30d.

One of: 1d, 7d, 30d, 90d

currencyqueryno3-letter currency code. Defaults to the profile FK / USD.

Request

curl "https://api.scmm.app/api/inventory/<profileId>/history?period=1d"

Response 200 · application/json · array<object>

  • timestamp*string
  • totalValue*number

Purchase price, market price and return per item

GET/api/inventory/{profileId}/investmentAuth required

Requires authentication, and answers 401 for anyone but the inventory owner or an administrator. Rows are one per inventory item (never grouped by skin) because the purchase price is recorded per item. Every money field is in the resolved display currency.

Parameters

NameInRequiredDescription
profileIdpathyesProfile id, profile guid, vanity url, or 64-bit Steam id.
searchquerynoFilter by item name or type.
sortByquerynoColumn to sort on. Defaults to buyPrice.

One of: name, buyPrice, marketPrice, marketFee, profit, roi

sortDirectionquerynoSort direction. Defaults to desc.

One of: asc, desc

pagequeryno1-based page index. Defaults to 1.
pageSizequerynoItems per page. Defaults to 25, max 100.
currencyqueryno3-letter currency code. Defaults to the profile FK / USD.

Request

curl "https://api.scmm.app/api/inventory/<profileId>/investment?search=<search>" \
  -H "x-api-key: YOUR_KEY_HERE"

Response 200 · application/json · object

  • array<object>
  • page*number
  • pageSize*number
  • total*number

    Matching rows (inventory assets). Smaller than totalUnits when the profile holds a stack.

  • totalUnits*number

    Matching units — sum(quantity), the figure the page header calls "Inventory Items".

  • hasMore*boolean
  • currency*string
  • includeMarketFees*boolean

Invested, gains, losses and net return, totalled

GET/api/inventory/{profileId}/investment/totalsAuth required

Requires authentication, and answers 401 for anyone but the inventory owner or an administrator. Every figure is null until more than half the inventory carries purchase data — below that the numbers describe the missing data, not the portfolio.

Parameters

NameInRequiredDescription
profileIdpathyesProfile id, profile guid, vanity url, or 64-bit Steam id.
currencyqueryno3-letter currency code. Defaults to the profile FK / USD.

Request

curl "https://api.scmm.app/api/inventory/<profileId>/investment/totals?currency=USD" \
  -H "x-api-key: YOUR_KEY_HERE"

Response 200 · application/json · object

  • invested*number | null
  • gains*number | null
  • losses*number | null
  • netReturn*number | null
  • currency*string
  • itemsWithBuyPrices*number
  • itemCount*number
  • hasSetupInvestment*boolean

Performance overview for one profile inventory

GET/api/inventory/{profileId}/performance

The summary numbers for one inventory over the chosen window: its current value, the change across the window, the best and worst movers and the per-collection deltas. It is the overview the more specific performance/* endpoints break down.

Every figure is computed over period, which defaults to 24h — the shortest window and the noisiest, so widen it before drawing conclusions. It answers 401 when the profile has opted out of item analytics and the caller is neither its owner nor an administrator.

Parameters

NameInRequiredDescription
profileIdpathyesProfile id, profile guid, vanity url, or 64-bit Steam id.
periodquerynoWindow: 24h, 7d, 30d, 90d. Defaults to 24h.

One of: 24h, 7d, 30d, 90d

Request

curl "https://api.scmm.app/api/inventory/<profileId>/performance?period=24h"

Response 200 · application/json · object

  • object
  • array<object>
  • array<object>
  • historyBackfillPending*boolean

Per-collection performance over a window

GET/api/inventory/{profileId}/performance/collections

One row per collection the profile owns from, with its value and its value-weighted change over the window. Value-weighted rather than a simple mean, so a collection’s change reflects what the holder actually owns rather than treating one cheap item as equal to a set.

It answers 401 when the profile has opted out of item analytics and the caller is neither its owner nor an administrator.

Parameters

NameInRequiredDescription
profileIdpathyesProfile id, profile guid, vanity url, or 64-bit Steam id.
periodquerynoWindow: 24h, 7d, 30d, 90d. Defaults to 24h.

One of: 24h, 7d, 30d, 90d

Request

curl "https://api.scmm.app/api/inventory/<profileId>/performance/collections?period=24h"

Response 200 · application/json · array<object>

  • name*string
  • iconUrl*string | null
  • value*number
  • changePercent*number | null
  • currency*string
  • ownedCount*number
  • totalCount*number

Wipe and store-release events in a window

GET/api/inventory/{profileId}/performance/events

Dated game events inside the same window a performance series covers: monthly force wipes and item-store releases. They are returned separately from the series so a client can annotate it without a second time axis, and they explain most of the sharp moves in one.

It answers 401 when the profile has opted out of item analytics and the caller is neither its owner nor an administrator.

Parameters

NameInRequiredDescription
profileIdpathyesProfile id, profile guid, vanity url, or 64-bit Steam id.
daysquerynoTrailing window in days. 0 returns all time. Defaults to 30.

Request

curl "https://api.scmm.app/api/inventory/<profileId>/performance/events?days=30"

Response 200 · application/json · array<object>

  • kind*enum

    wipe for a monthly force wipe, store for an item store release

    One of: wipe, store

  • label*string
  • timestamp*string

Inventory performance report as CSV

GET/api/inventory/{profileId}/performance/export

The same figures as the performance endpoints, as a CSV file: the portfolio summary followed by one row per owned item. Responds text/csv as an attachment rather than JSON, and the figures are computed over period in the resolved currency.

It answers 401 when the profile has opted out of item analytics and the caller is neither its owner nor an administrator.

Parameters

NameInRequiredDescription
profileIdpathyesProfile id, profile guid, vanity url, or 64-bit Steam id.
periodquerynoWindow the figures are computed over: 24h, 7d, 30d, 90d. Defaults to 24h.

One of: 24h, 7d, 30d, 90d

currencyqueryno3-letter currency code. Defaults to the profile FK / USD.

Request

curl "https://api.scmm.app/api/inventory/<profileId>/performance/export?period=24h"

Portfolio value series over a trailing window

GET/api/inventory/{profileId}/performance/history

One point per day of the profile’s portfolio value, oldest first, over a trailing window in days. days=0 returns the whole recorded history. A day with no snapshot is absent rather than zero.

It answers 401 when the profile has opted out of item analytics and the caller is neither its owner nor an administrator.

Parameters

NameInRequiredDescription
profileIdpathyesProfile id, profile guid, vanity url, or 64-bit Steam id.
daysquerynoTrailing window in days. 0 returns all time. Defaults to 30.

Request

curl "https://api.scmm.app/api/inventory/<profileId>/performance/history?days=30"

Response 200 · application/json · array<object>

  • timestamp*string
  • value*number

Per-item performance for one profile inventory

GET/api/inventory/{profileId}/performance/items

One row per item held, with its price, its change over the window, its recent volume and a sparkline of its recent levels — paginated, searchable and sortable. It is the detail behind the performance overview.

It answers 401 when the profile has opted out of item analytics and the caller is neither its owner nor an administrator.

Parameters

NameInRequiredDescription
profileIdpathyesProfile id, profile guid, vanity url, or 64-bit Steam id.
periodquerynoWindow: 24h, 7d, 30d, 90d. Defaults to 24h.

One of: 24h, 7d, 30d, 90d

searchquerynoFilter by item name or type.
sortquerynoSort order. Defaults to price_desc.

One of: price_asc, price_desc, change_asc, change_desc, liquidity_asc, liquidity_desc, volume_asc, volume_desc

pagequeryno1-based page index. Defaults to 1.
pageSizequerynoItems per page. Defaults to 24, max 100.

Request

curl "https://api.scmm.app/api/inventory/<profileId>/performance/items?period=24h"

Response 200 · application/json · object

  • array<object>
  • total*number
  • page*number
  • pageSize*number

Profile summary for an inventory

GET/api/inventory/{profileId}/summary

Who an inventory belongs to, in one small response: Steam id, name, avatar and when the inventory was last imported. It is the header a client needs before any of the heavier inventory reads, and it accepts the same identifier forms they do.

It answers 401 when the profile has opted out of item analytics and the caller is neither its owner nor an administrator.

Parameters

NameInRequiredDescription
profileIdpathyesProfile id, profile guid, vanity url, or 64-bit Steam id.

Request

curl "https://api.scmm.app/api/inventory/<profileId>/summary"

Response 200 · application/json · object

  • profileId*string
  • steamId*string
  • name*string | null
  • avatarUrl*string | null
  • lastUpdatedInventoryOn*string | null
  • privacy*number
  • lastImportError*string | null

    Why the most recent attempt to refresh this inventory did not finish, or null if it did. lastUpdatedInventoryOn stays the truth about the snapshot; this is the truth about the attempt to move it, and the two are independent — a non-null value here does not mean the inventory is unreadable.

    Prose meant for display, not a code to branch on: the wording is chosen to be shown to a person and can change without notice.

  • refreshSuggested*boolean

    true when this inventory's stored snapshot is past the staleness threshold and nothing has attempted to refresh it in the last hour — that is, asking us to refresh it now would actually do something. false whenever lastUpdatedInventoryOn is null: an inventory that has never been imported is a different flow, not a very stale one.

    A suggestion, not a promise. It answers a question about this inventory alone; a refresh can still be declined for reasons that have nothing to do with it, such as a site-wide budget, and the value is recomputed per request rather than stored.

Current total value of a profile inventory

GET/api/inventory/{profileId}/value

The current total market value of one inventory, plus the item counts behind it. This is the cheapest inventory read and the one to poll; the item list and the performance endpoints answer the same question in far more detail and cost accordingly.

It answers 401 when the profile has opted out of item analytics and the caller is neither its owner nor an administrator.

Parameters

NameInRequiredDescription
profileIdpathyesProfile id, profile guid, vanity url, or 64-bit Steam id.
marketquerynoValuation market. steam (default) prices via the Steam Community Market and refreshes the stored snapshot; a MarketType key returns a view-only total under that market’s listing prices.
currencyqueryno3-letter currency code. Defaults to the profile FK / USD.

Request

curl "https://api.scmm.app/api/inventory/<profileId>/value?market=steam"

Response 200 · application/json · object

  • totalValue*number
  • itemCount*number
  • currency*string
  • profitLoss*number

Resolve a Steam id and queue an inventory import

POST/api/inventory/calculate

Resolves the identifier to a 64-bit Steam id (resolving vanity names via Steam when needed), creates a lightweight profile on first sight, and enqueues an inventory import. The import runs asynchronously — poll GET /api/inventory/import-status/{steamId} with the returned steamId until importing is false. Anonymous callers share an hourly import budget (per address and site-wide); when it is spent the response is 429 with a Retry-After header for the top of the hour. A caller signed in with a session is not subject to that budget. An x-api-key header is not a session on this route, so a key holder is budgeted as an anonymous caller.

Request body application/json · required

  • identifier*string

Request

curl -X POST "https://api.scmm.app/api/inventory/calculate" \
  -H "Content-Type: application/json" \
  -d '{"identifier":"<identifier>"}'

Response 200 · application/json · object

  • profileId*string

    Profile id to use on the other inventory endpoints.

  • steamId*string

    The 64-bit Steam id the identifier resolved to.

  • name*string | null
  • avatarUrl*string | null
  • privacy*number

    Steam community visibility: 0 unknown, 1 private, 2 friends-only, 3 public.

  • queued*boolean

    Whether this call queued an import. false when nothing new was queued, for example because the inventory was refreshed recently. Poll GET /api/inventory/import-status/{steamId} either way.

Combine every stack of each item in your inventory

PUT/api/inventory/combine-allAuth required

Requires authentication and your own Steam Web API key. For every item held in more than one stack, moves each other stack into the stack with the lowest asset id on Steam, one stack at a time. Stacks that cannot be traded or sold are left out unless stackUntradableAndUnmarketable is true. One request makes at most 25 changes, starts none after 45 seconds, and returns how many it made and how many remain; repeat it until remaining is 0. Each change Steam confirms is saved even if a later one fails. To see how many stacks it would combine before making any change, and without a key, use GET /api/inventory/combine-all/plan. Answers 400 when Steam refuses the change (its reason is in message), 401 when Steam rejects the key, 409 when another stack change is running for your inventory, 429 when Steam is rate-limiting the key, and 502 when Steam did not confirm a change; after a 502, queue a fresh import with POST /api/inventory/import/{steamId}?force=true and poll GET /api/inventory/import-status/{steamId} until importing is false before retrying, because the change may have happened. This request is not safe to retry automatically: a repeated request repeats the change, so turn off automatic retries for it in your HTTP client. Instead, after a timeout or a 502, import the inventory and read the stacks again before sending anything. Allow at least 60 seconds per request.

Parameters

NameInRequiredDescription
steam-api-keyheaderyesYour own Steam Web API key, from https://steamcommunity.com/dev/apikey. It is used for this one request to Steam and is not stored.

Request body application/json · optional

  • stackUntradableAndUnmarketablebooleanoptional

    Also combine stacks that cannot currently be traded or sold. The combined stack takes on their restriction.

Request

curl -X PUT "https://api.scmm.app/api/inventory/combine-all" \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_KEY_HERE" \
  -H "steam-api-key: YOUR_STEAM_API_KEY" \
  -d '{"stackUntradableAndUnmarketable":true}'

Response 200 · application/json · object

  • combined*number

    Stacks combined by this request.

  • remaining*number

    Stacks still to combine. Repeat the request until this is 0.

Preview what combining every stack would do

GET/api/inventory/combine-all/planAuth required

Requires authentication; no Steam Web API key, and nothing is sent to Steam. Returns how many items in your inventory are held in more than one stack and how many stacks PUT /api/inventory/combine-all would move into another stack, with the same rules and the same stackUntradableAndUnmarketable default. stacks is the number of changes that operation would make, so it is the combined plus remaining of its first response if nothing changes in between. requests is the fewest requests that operation takes at 25 changes each; it takes more when a request stops at its 45-second budget. All three are 0 when there is nothing to combine.

Parameters

NameInRequiredDescription
profileIdquerynoProfile id or 64-bit Steam id whose inventory to preview. Administrators only; anyone else naming another profile gets 404. Omit it for your own inventory.
stackUntradableAndUnmarketablequerynoAlso count stacks that cannot currently be traded or sold. Defaults to false.

One of: true, false

Request

curl "https://api.scmm.app/api/inventory/combine-all/plan?stackUntradableAndUnmarketable=true" \
  -H "x-api-key: YOUR_KEY_HERE"

Response 200 · application/json · object

  • items*number

    Items held in more than one stack. Each would end in a single stack.

  • stacks*number

    Stacks that would be moved into another stack of the same item, one change each.

  • requests*number

    The fewest PUT /api/inventory/combine-all requests the combine takes. Each request makes a limited number of changes and also stops when its time budget runs out, so it can take more requests than this, never fewer.

  • purchaseRecordsReplaced*number

    How many of the stacks that would be moved carry a purchase price or acquisition you recorded. Combining deletes those records: the combined units keep only the purchase record of the stack they are moved into.

Status of an on-demand inventory import

GET/api/inventory/import-status/{steamId}

The poll target for an import queued by POST /api/inventory/calculate. It reports whether the import has produced a value, is still running, or finished without one — the third case is a real answer rather than a failure, and it is what a private or empty Steam inventory returns. Takes a 64-bit Steam id.

Parameters

NameInRequiredDescription
steamIdpathyes64-bit Steam id.

Request

curl "https://api.scmm.app/api/inventory/import-status/<steamId>"

Response 200 · application/json · object

  • status*enum

    pending while the first import is queued or running, ready once an import has completed (the inventory may be empty), unavailable when an import finished without producing one. It does not say why.

    One of: pending, ready, unavailable

  • importState*enum | null

    waiting while the import is queued, active while it runs, and null when nothing is pending. How long a queued import waits depends on the queue, so only an active one is worth timing.

    One of: waiting, active

  • importing*boolean

    Whether an import is queued or running right now, including a refresh of an inventory that is already ready.

  • importRunningForMs*number | null

    How long the active import has been running, in milliseconds, or null when none is. A duration rather than a start time, so it can be measured against the caller's own clock.

  • lastUpdatedInventoryOn*string | null

    When the last import completed, ISO-8601, or null if none has.

  • lastImportError*string | null

    Why the most recent import attempt did not complete, or null. Meant to be shown, not parsed: the wording can change.

Queue an inventory import from Steam

POST/api/inventory/import/{steamId}Auth required

Queues a refresh of the profile’s Steam inventory and returns at once. Poll GET /api/inventory/import-status/{steamId} until importing is false; a newer lastUpdatedInventoryOn means the import completed. Requires authentication. Throttled to once per hour per profile; force=true skips that hourly window but is honored only for the owner of the Steam id (or an admin) — other callers stay throttled. Even forced, one profile is queued at most once a minute, and a request made while an import for that profile is already queued or running answers queued: false with reason: "already-running" rather than adding a second one.

Parameters

NameInRequiredDescription
steamIdpathyes64-bit Steam id.
forcequerynoOwner/admin only: skip the 1-hour sync throttle and queue a fetch from Steam now.

Request

curl -X POST "https://api.scmm.app/api/inventory/import/<steamId>?force=false" \
  -H "x-api-key: YOUR_KEY_HERE"

Response 200 · application/json · object

  • reasonenumoptional

    Why nothing was queued, present only when queued is false. already-running: an import for this profile is already queued or running, so poll for that one. too-soon: a forced import started less than a minute ago. fresh: the inventory was imported within the last hour and force was not honored for this caller. recently-attempted: an import was attempted within the last hour and did not complete.

    One of: already-running, too-soon, fresh, recently-attempted

  • queued*boolean

    true when an import was queued for this profile. Poll GET /api/inventory/import-status/{steamId} until importing is false; a newer lastUpdatedInventoryOn there means the import completed.

Update purchase price or source for one item

PUT/api/inventory/item/{itemId}Auth required

Requires authentication, and answers 401 unless the item belongs to you (or you are an administrator). Absent fields are left unchanged; an explicit null clears one. Choosing a free acquisition source (gambling, gift, game drop) always clears the price.

Parameters

NameInRequiredDescription
itemIdpathyesSteam asset id of the inventory item, as returned in itemId.

Request body application/json · required

  • buyPricenumber | nulloptional
  • currencystring | nulloptional
  • acquiredBynumberoptional

Request

curl -X PUT "https://api.scmm.app/api/inventory/item/<itemId>" \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_KEY_HERE" \
  -d '{"buyPrice":0}'

Combine stacks of one item into another stack

PUT/api/inventory/item/{itemId}/combineAuth required

Requires authentication and your own Steam Web API key. Moves the given quantity of each source stack into the destination stack on Steam, one source at a time in body order, and returns the item’s stacks afterwards. A source that is not in your inventory is skipped. One request makes at most 25 changes and starts none after 45 seconds; unreached lists the source asset ids it did not reach, so send exactly those again, with the same quantities, until it is empty. Each change Steam confirms is saved even if a later source fails. Answers 400 when Steam refuses the change (its reason is in message), 401 when Steam rejects the key, 409 when another stack change is running for your inventory, 429 when Steam is rate-limiting the key, and 502 when Steam did not confirm a change; after a 502, queue a fresh import with POST /api/inventory/import/{steamId}?force=true and poll GET /api/inventory/import-status/{steamId} until importing is false before retrying, because the change may have happened. This request is not safe to retry automatically: a repeated request repeats the change, so turn off automatic retries for it in your HTTP client. Instead, after a timeout or a 502, import the inventory and read the stacks again before sending anything. Allow at least 60 seconds per request.

Parameters

NameInRequiredDescription
itemIdpathyesSteam asset id of the destination stack.
steam-api-keyheaderyesYour own Steam Web API key, from https://steamcommunity.com/dev/apikey. It is used for this one request to Steam and is not stored.

Request body application/json · required

  • *map<string, integer>

    Each key is a source stack’s asset id (1 to 20 digits), not the destination; each value is the whole number of units to move from it, at least 1 and no more than that stack holds.

Request

curl -X PUT "https://api.scmm.app/api/inventory/item/<itemId>/combine" \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_KEY_HERE" \
  -H "steam-api-key: YOUR_STEAM_API_KEY" \
  -d '{"<key>":1}'

Response 200 · application/json · object

  • descriptionId*string | null

    The item the stacks share, or null for an asset whose item could not be identified.

  • array<object>

    Every stack of the item in your inventory, largest first.

  • remaining*number

    How many source stacks were not reached: the length of unreached. 0 when every source was attempted.

  • unreached*array<string>

    Source asset ids this request did not reach, in the order it read them from the body. Send exactly these again, with the same quantities, until the list is empty.

Split a stack into new stacks

PUT/api/inventory/item/{itemId}/splitAuth required

Requires authentication and your own Steam Web API key. Moves quantity units out of the stack on Steam, into one new stack when stackNewItems is true (the default) or into single items when it is false, and returns the item’s stacks afterwards. quantity must be less than the stack holds. A split into single items makes at most 25 changes per request and starts none after 45 seconds; remaining says how many units are left, so repeat the request with that quantity until it is 0. Answers 400 when Steam refuses the change (its reason is in message), 401 when Steam rejects the key, 409 when another stack change is running for your inventory, 429 when Steam is rate-limiting the key, and 502 when Steam did not confirm a change; after a 502, queue a fresh import with POST /api/inventory/import/{steamId}?force=true and poll GET /api/inventory/import-status/{steamId} until importing is false before retrying, because the change may have happened. This request is not safe to retry automatically: a repeated request repeats the change, so turn off automatic retries for it in your HTTP client. Instead, after a timeout or a 502, import the inventory and read the stacks again before sending anything. Allow at least 60 seconds per request.

Parameters

NameInRequiredDescription
itemIdpathyesSteam asset id of the stack to split.
steam-api-keyheaderyesYour own Steam Web API key, from https://steamcommunity.com/dev/apikey. It is used for this one request to Steam and is not stored.

Request body application/json · required

  • quantity*integer

    Units to move out of the stack.

  • stackNewItemsbooleanoptional

    Whether the moved units form one stack (true) or single items (false).

Request

curl -X PUT "https://api.scmm.app/api/inventory/item/<itemId>/split" \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_KEY_HERE" \
  -H "steam-api-key: YOUR_STEAM_API_KEY" \
  -d '{"quantity":1}'

Response 200 · application/json · object

  • descriptionId*string | null

    The item the stacks share, or null for an asset whose item could not be identified.

  • array<object>

    Every stack of the item in your inventory, largest first.

  • remaining*number

    Units still to split. Repeat the request with this quantity until it is 0; always 0 when the units stack.

List your stacks of one inventory item

GET/api/inventory/item/{itemId}/stacksAuth required

Requires authentication. Returns every stack of the same item in your own inventory, largest first, with its asset id, quantity and trade lock. Answers 404 for an asset id that is not in your inventory.

Parameters

NameInRequiredDescription
itemIdpathyesSteam asset id of any one stack of the item, as returned in assetId.
profileIdquerynoProfile id or 64-bit Steam id whose stacks to list. Administrators only; anyone else naming another profile gets 404. Omit it for your own inventory.

Request

curl "https://api.scmm.app/api/inventory/item/<itemId>/stacks" \
  -H "x-api-key: YOUR_KEY_HERE"

Response 200 · application/json · object

  • descriptionId*string | null

    The item the stacks share, or null for an asset whose item could not be identified.

  • array<object>

    Every stack of the item in your inventory, largest first.

Highest-valued inventories, paginated and sortable

GET/api/inventory/leaderboard

Serves the top 500 inventories for the requested sort, not every inventory tracked. total is therefore capped at 500 and totalPages with it; pages beyond the cap return no entries. For the true, uncapped count of valued inventories use inventoriesTracked on GET /api/inventory/leaderboard/stats.

Parameters

NameInRequiredDescription
pagequeryno1-based page index. Defaults to 1. Pages past the 500-row cap return no entries.
pageSizequerynoRows per page. Defaults to 50, max 100.
sortquerynoSort key. Defaults to value.

One of: value, time, amount

dirquerynoSort direction. Defaults to desc.

One of: asc, desc

Request

curl "https://api.scmm.app/api/inventory/leaderboard?page=1"

Response 200 · application/json · object

  • array<object>
  • page*number
  • pageSize*number
  • total*number

    Rows the board serves, capped at 500. The uncapped number of valued inventories is inventoriesTracked on GET /api/inventory/leaderboard/stats.

  • totalPages*number

    total divided by pageSize, rounded up, so it is capped with total.

  • currency*string

    Always USD: totalValue is not converted.

Inventory totals across every valued profile

GET/api/inventory/leaderboard/stats

Uncapped on purpose — this is the counterpart to the leaderboard's 500-row cap, and the only place the true number of valued inventories is published.

Request

curl "https://api.scmm.app/api/inventory/leaderboard/stats"

Response 200 · application/json · object

  • inventoriesTracked*number

    Every inventory with a market value, uncapped.

  • totalItems*number

    Items across those inventories.

  • totalMarketValue*number

    Their combined market value, in USD hundredths.

  • currency*string

    Always USD.

Item ids in the authenticated user’s own inventory

GET/api/inventory/owned/item-idsAuth required

Lightweight companion to the inventory list — hydrates the "Owned" badge across the app. Distinct ids only, no quantities and no ordering. Scoped to the resolved app, so a Rust session never reports a CS2 item.

Request

curl "https://api.scmm.app/api/inventory/owned/item-ids" \
  -H "x-api-key: YOUR_KEY_HERE"

Response 200 · application/json · array<string>

  • *array<string>