Migrating from the v1 API
The legacy API at rust.scmm.app is replaced by this one. This page maps what you call today onto what you should call now — all 69 v1 endpoints, including the ones with no successor.
Nothing here is a pure rename. The service was rewritten, so paths moved, ids changed type and one whole parameter disappeared. Read the four breaking changes first: they affect every request you make, and three of them fail silently.
Four things that change everywhere
1. Item ids changed type. v1 ids were 64-bit integers (the Steam assetDescriptionId). v2 ids are GUID strings. Every id you have stored is dead — a v1 id will not resolve.
There are two ways out, and the second is better. The v2 item DTO still carries assetDescriptionId, so you can sweep the catalogue once, build an assetDescriptionId → guid table and rewrite your keys. Or stop storing ids: every endpoint that takes an item id also accepts the item’s nameHash — the same value v1 returned as marketHashName — which is stable across both APIs and readable in a log. Note the rename: v2 has no marketHashName field, so a straight port of that property access yields undefined.
curl "https://api.scmm.app/api/item/f6f4f1a2-1c3a-4b6e-9d0f-0a1b2c3d4e5f"
curl "https://api.scmm.app/api/item/Tempered%20AK47"2. Money is integer hundredths, always. v1 gave you a per-currency scale and expected value / 10^scale. v2 is value / 100 for every currency, with no exceptions.
This is the one that will bite. scale is still on the currency DTO, so your v1 code keeps compiling and keeps producing the right answer for every currency where scale happens to be 2 — which is most of them. Delete the exponent at the boundary rather than leaving it to be discovered by a user in a currency you did not test.
3. language is gone. Every v1 path accepted language and currency. v2 has no language parameter anywhere and no /api/language endpoint. Strip it. currency survives, with the same 3-letter codes, on the endpoints that return money — but PLN and RUB no longer exist, so a hard-coded one now falls back to USD instead of erroring.
4. Pagination changed shape. v1 took start/count and returned { items, start, count, total }. v2 takes page (1-based)/pageSize and returns { items, page, pageSize, total }.
Not uniform yet: GET /api/marketplace/deals still takes start/count. The reference is authoritative per endpoint.
Authentication
v1 was fully open and said so. v2 keeps that for reads: the catalogue, prices, stores, workshop and inventory endpoints still take no credential. What changed is that the handful of endpoints touching your own account — /api/profile/me, /api/profile/data, inventory import — now need an x-api-key header or a session cookie. See Authentication.
Items
| v1 | Now |
|---|---|
| /api/item | /api/itemstart/count → page/pageSize. exactMatch dropped. sortBy/sortDirection → a single sort key (name_asc, price_desc, newest, …) — v1 sorted by DTO property name, so those values do not carry over and an unrecognised one silently returns the default order. If you wrote name, -name, price, -price, -subscriptions, timeAccepted or -timeAccepted against an earlier version of the reference, those still work as aliases; the reference lists the canonical keys and names the aliases. |
| /api/item/{id} | /api/item/{id}id is now a GUID or nameHash (v1: marketHashName), not the int64 id. |
| /api/item/collection/{name} | /api/item/collection/{name} |
| /api/item/filters | /api/item/types + /api/item/collections + /api/item/colorsv1 returned the Steam Community Market filter facets, which v2 does not serve; the values the /api/item filters accept come from these three endpoints. |
| /api/item/types | /api/item/types |
| /api/item/type/{type}/demand | /api/item/type/{type}/demand |
| /api/item/definitionArchives | /api/item/definitionArchives |
| /api/item/definitionArchive/{digest} | /api/item/definitionArchive/{digest} |
| /api/item/definitionArchive/{oldDigest}/compareTo/{newDigest} | /api/item/definitionArchive/{oldDigest}/compareTo/{newDigest} |
| /api/item/{id}/buyOrders | /api/item/{id}/buyOrders |
| /api/item/{id}/sellOrders | /api/item/{id}/sellOrders |
| /api/item/{id}/sales/market | /api/item/{id}/sales/marketDaily points. ochl=true still fills open, high, low and close; without it they are null, where v1 sent 0. |
| /api/item/{id}/sales/store | /api/item/{id}/sales/store |
| /api/item/{id}/topHolders | /api/item/{id}/topHolders |
| /api/item/market-cap/items | /api/statistics/marketCap/items |
| /api/item/market-cap/summary | /api/statistics/marketCap |
| /api/item/prices | /api/item/pricesStill one response for the whole catalogue. One shape now, not two: markets= adds a per-market prices[] rather than switching the response type. Join on nameHash. The offer quoted is the cheapest, where v1 preferred Steam. |
Marketplace, store and workshop
| v1 | Now |
|---|---|
| /api/marketplace/items | /api/marketplace/items |
| /api/store | /api/store |
| /api/store/current | /api/store/current |
| /api/store/{id} | /api/store/{id} |
| /api/store/nextUpdateTime | /api/store/nextUpdateTime |
| /api/store/{id}/stats/itemRevenue | /api/statistics/store/{id}/itemRevenue |
| /api/store/{id}/stats/itemSales | /api/statistics/store/{id}/itemSales |
| POST /api/store/{id}/linkItem | goneAdministrator or contributor only, as in v1, and not part of the published API. |
| POST /api/store/{id}/unlinkItem | goneAdministrator or contributor only, as in v1, and not part of the published API. |
| /api/workshop | /api/workshopShape change: submissions, accepted or not, are grouped by the week they were published (Friday to Thursday, UTC) and paged by week, not a flat paginated list. |
Profile and inventory
| v1 | Now |
|---|---|
| /api/profile | /api/profile/me |
| PUT /api/profile | PATCH /api/profile/me + PATCH /api/profile/me/preferences |
| /api/profile/data | /api/profile/data |
| DELETE /api/profile/data | DELETE /api/profile/data |
| /api/profile/{id}/summary | /api/profile/{id} |
| /api/profile/{id}/inventory/items | /api/inventory/{profileId}Now paginated — returns a page object, not an array. |
| /api/profile/{id}/inventory/total | /api/inventory/{profileId}/value |
| /api/profile/{id}/inventory/value | /api/inventory/{profileId}/valueMosaic generation dropped (generateInventoryMosaic and its mosaic* params). |
| /api/profile/{id}/inventory/collections | /api/inventory/{profileId}/collections |
| /api/profile/{id}/inventory/investment | /api/inventory/{profileId}/investmentRequires auth: the profile owner or an administrator. filter → search, start/count → page/pageSize. |
| /api/profile/{id}/inventory/movement | /api/inventory/{profileId}/performance |
| /api/profile/{id}/inventory/timeline | /api/inventory/{profileId}/history |
| /api/profile/{id}/inventory/owned | goneReturned unique owned class ids for any profile. The nearest v2 route, GET /api/inventory/owned/item-ids, covers only the signed-in caller and returns item ids. |
| POST /api/profile/{id}/inventory/sync | POST /api/inventory/import/{steamId}Now requires auth. force is honoured only for the owner, an administrator or a contributor. |
| PUT /api/profile/{profileId}/inventory/combineAll | PUT /api/inventory/combine-allActs on your own inventory, so there is no profileId. stackUntradableAndUnmarketable moves from the query string to the JSON body. One request combines a batch and returns remaining; repeat it until remaining is 0. v1 answered an empty 200; v2 returns combined and remaining. Needs the account owner’s own Steam Web API key in the steam-api-key header; it is used for that one request and not stored. |
| PUT /api/profile/{profileId}/inventory/item/{itemId} | PUT /api/inventory/item/{itemId}The buy-price setter. No profileId in the path; itemId is still the Steam asset id. Owner or administrator only. |
| PUT /api/profile/{profileId}/inventory/item/{itemId}/combine | PUT /api/inventory/item/{itemId}/combineSame body: source asset ids mapped to quantities. v1 returned the destination stack as one object; v2 returns descriptionId, every stack of the item, remaining, and unreached, the source asset ids not reached; send exactly those again until it is empty. On each stack, steamId is now assetId. An item that is not yours answers 404, where v1 answered 401. Needs the account owner’s own Steam Web API key in the steam-api-key header; it is used for that one request and not stored. |
| PUT /api/profile/{profileId}/inventory/item/{itemId}/split | PUT /api/inventory/item/{itemId}/splitThe body is { quantity, stackNewItems } instead of a bare number and ?stackNewItems=. v1 returned only the new stacks; v2 returns every stack of the item, and remaining; repeat with that quantity until it is 0. On each stack, steamId is now assetId. An item that is not yours answers 404, where v1 answered 401. Needs the account owner’s own Steam Web API key in the steam-api-key header; it is used for that one request and not stored. |
Statistics
| v1 | Now |
|---|---|
| /api/stats/market/totals | /api/statistics/market |
| /api/stats/market/indexFund | /api/statistics/indexFund |
| /api/stats/market/cheapestListings | /api/marketplace/dealsDefault mode. |
| /api/stats/market/flips | /api/marketplace/deals?mode=flips |
| /api/stats/market/cheapestCraftableContainerCosts | /api/marketplace/crafting |
| /api/stats/market/cheapestCraftingResourceCosts | /api/marketplace/crafting |
| /api/stats/store/topSellers | /api/statistics/store/{id}/topSellersNow per store, not global. |
| /api/stats/profiles/inventories/highestValue | /api/inventory/leaderboard |
| /api/stats/profiles/inventories/total | /api/inventory/leaderboard/stats |
| /api/stats/items/mostExpensive | /api/item?sort=price_descsort takes a fixed key, not a v1 property name. See the /api/item row. |
| /api/stats/items/mostSupply | /api/item?sort=supply_descRanked by estimated supply. /api/statistics/supply has the per-category totals. |
| /api/stats/items/mostDemanded | /api/item?sort=demand_descSame ordering as v1: 24-hour sales, highest first. |
| /api/stats/market/activity | gone |
| /api/stats/items/allTimeHigh | /api/item?sort=ath_descRanks every item by its lowest ask against its all-time high, not only the items at it. |
| /api/stats/items/allTimeLow | /api/item?sort=atl_ascRanks every item by its lowest ask against its all-time low, not only the items at it. |
| /api/stats/items/largestCollections | gone |
| /api/stats/items/mostSaturated | gone |
| /api/stats/items/typeDistribution | gone |
| /api/stats/profiles/inventories/myRank | gone |
| /api/stats/profiles/inventories/recentlyValued | /api/inventory/leaderboard?sort=timeMost recently valued first; the top 500 only. |
| /api/stats/profiles/largestCreators | gone |
| /api/stats/contributors | gone |
| /api/stats/donators | gone |
App, currency and language
| v1 | Now |
|---|---|
| /api/app | /api/app + /api/app/currentdetailed dropped. |
| /api/currency | /api/currencydetailed=true still adds guid and exchangeRateMultiplier. Exchange-rate history is at /api/currency/{id}/exchange-rates, with the numeric id. PLN and RUB removed. |
| /api/language | goneNo language negotiation in v2. |
The bulk price feed is back — with different numbers
GET /api/item/prices returns the whole catalogue in one response, at the same path it had in v1. If you were sweeping GET /api/item page by page as a workaround, stop — that is ~62 requests against a 100/minute limit and this is one.
Three differences matter, and the third changes your numbers without changing your code.
- One response shape, not two. v1 returned a different object depending on whether you passed
markets. v2 always returns the same object; passingmarkets=adds apricesarray to it and restricts which markets are considered. - Your stored ids still work here. This is the one endpoint where they do: v1’s
idon this response was the Steam class id, not theassetDescriptionIdused elsewhere, and v2 returns it asclassId.nameHashis still the better key to move to. - The price is the cheapest one, not Steam’s. v1 sorted first-party sources ahead of every third-party market regardless of price, so it reported Steam’s number even when somewhere else was far cheaper. v2 reports the genuinely cheapest offer and names it in
marketType.
Expect most prices to drop when you switch. Measured across our catalogue: of the items quoted by both Steam and a third-party market, a third party was cheaper on 96% of them, by a median of 37%. If you have alerting on price movement, expect it to fire on the day you migrate. If you specifically want Steam’s price, ask for it: ?markets=SteamCommunityMarket.
Compare on price + fee. v1 gave you a bare price. v2 also returns fee — our model of what buying on that market really costs — and it is often negative, because some markets sell below face value through bonus balance. Two other small changes in the same family: an item nothing sells is price: null where v1 said 0, and supply is null when the market reports no count.
Gaps, stated plainly
Three things v1 did that v2 does not. We would rather you read it here than find out in production.
- Eight statistics endpoints have no successor — the
typeDistribution/largestCollections/mostSaturatedfamily, market activity, the rank and creator leaderboards, and contributors/donators. - Inventory mosaics are gone. Inventory value returns a number, not an image.
- Workshop search changed shape, not just its path — submissions, accepted or not, come back grouped by the week they were published rather than as a flat list.
Tell us which gap blocks you, in our Discord. That is what drives the order we close them in.
New in v2
| Endpoint | What it does |
|---|---|
| /api/inventory/item/{itemId}/stacks | Every stack of one item in your own inventory, with each stack’s asset id, quantity and trade lock: what a combine or split request needs. |
| /api/inventory/combine-all/plan | How many items and stacks PUT /api/inventory/combine-all would combine, without a Steam Web API key and without changing anything. |
| /api/item/{id}/prices/history | One item’s price history — a series per third-party market. See the per-market transpose below for one market’s whole catalogue. |
| /api/item/creator/{steamId} | A Steam creator's full item catalogue. |
| /api/marketplace/{market}/prices/history | One market's whole catalogue as a 30/60/90-day price aggregate, in a single request. Supports windows=, fields=, stats= and an incremental since= cursor. |
| /api/marketplace/{market}/sales-summary | A market's own completed-sale statistics (avgSalePrice, volume) — the sale side of the listing prices above. |
| /api/marketplace/markets | Per-market metadata: verified flags, bulk-buy support, CTA emphasis. |
| /api/marketplace/deals | Ranked deals and Steam flips in one endpoint. |
| /api/statistics/indexes | Every Rust skin index, valued at the latest snapshot. |
| /api/statistics/indexes/history | Daily level series per index. |
| /api/statistics/marketCap/history | Daily market-cap series with a badge summary. |
| /api/store/rotation | Active rotation timing for a countdown. |
| /api/statistics/store/{id}/subscribers | Cumulative workshop-subscriber timeline per store item. |
| /api/workshop/{id} | Full detail for one workshop submission. |
| /api/inventory/{profileId}/performance/* | Portfolio performance: history, events, collections, CSV. |
| /api/search | Cross-entity search over items, collections, stores, workshop submissions and profiles. |
| /api/game-events | Dated in-game events for annotating price charts. |
| /api/system/status | Per-market ingestion health and pipeline freshness. |
Suggested migration order
- Re-point the base URL to
https://api.scmm.appand delete everylanguageparameter. Both fail loudly, so do them first and let the errors guide you. - Fix money handling — divide by 100, delete
scale. Do this before anything that reads a price, because it fails silently. - Switch your item keys to
nameHash(v1’smarketHashName, renamed), or build theassetDescriptionId → guidremap. - Convert pagination to 1-based
page/pageSizeand read the new envelope. - Re-point the moved trees last — statistics (
/api/stats→/api/statistics) and inventory (/api/profile/{id}/inventory→/api/inventory/{profileId}).