New features
Stream: choose the event types you need witheventsGET /v1/listings/stream takes events, a comma-separated list of new, update, gone. update is about 80% of the feed, almost all price changes, so a client that only buys new listings and drops sold ones can use events=new,gone and read about a fifth of the bytes. Without the parameter you get all three, as before.Changes
- A stream connection that falls more than 8 MB behind is now closed instead of buffered without limit. Reconnect with
Last-Event-IDto continue from the last event you processed. If it keeps happening, narrow the feed witheventsand the other filters. - The stream now follows the one-listing-per-physical-skin rule of
GET /v1/listingsand the snapshot. When the same skin is on sale through more than one supplier, only the cheapest copy is announced asnew; a copy you hold is retired withgonewhen a cheaper one appears, and when the announced copy sells the next one arrives asnew. Before this, the stream announced every copy, so a client could hold two prices for one skin and see a listing the snapshot did not carry. Agonefor an id you never received is normal: ignore it. When this went live, every open connection was closed and the history before that moment was dropped, so a reconnect with an olderLast-Event-IDreceived oneresync: rebuild from the snapshot orGET /v1/listings, which already follow the rule, and the view is clean from there. - Fewer
item_unavailablerefusals at checkout: a listing whose live price moved past your ceiling is no longer treated as sold, and listings are checked with their supplier before the order is placed on every market that can answer, so an order is refused before any money moves rather than refunded after.
New features
Stream:update event for price changes and late float / patternGET /v1/listings/stream now sends update when a listing you already received changes price, or gets float_value / paint_seed filled in (some markets send these a few minutes after the lot goes on sale). The payload is the full Listing, same as new: replace your copy by id. With the snapshot plus new / update / gone you can keep the whole catalogue on your side instead of polling GET /v1/listings. min_price / max_price do not filter update, so a listing you hold that reprices out of your range still reaches you. Clients that ignore unknown event types keep working unchanged.Why an order failed: fail_reason, fail_side, fail_messageOrders now say why they failed, in GET /v1/orders, GET /v1/orders/info, GET /v1/orders/{id} and the order.updated webhook, P2P rows included. On a failed or cancelled order, fail_reason is a code to branch on, fail_side says who has to act (recipient, seller, buyer or market), and fail_message is one sentence saying what happened and what to do. All three are null on other statuses. Until now such an order read only as unavailable, even when the recipient’s inventory was private — a case only you can fix. The list of codes may grow: treat an unknown value as unavailable. See why an order failed.Rate limit headers on every answerEvery answer to a keyed call carries X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset and X-RateLimit-Bucket for the bucket it spent, so you can pace yourself before a 429. GET /v1/key lists every limit of the key under rate_limits.Changes
- Keyed calls are now metered only by the key’s own buckets. A site-wide per-IP limit of about 133 requests/min used to count them as well, so it capped every integration below the per-key numbers documented here, and its
x-ratelimit-limit: 1000header did not describe your key. Keyless endpoints (GET /v1/health,GET /v1/public/prices) are still limited per IP. See Rate limits. - The trading bucket is 3,000 requests/min per key, or the key’s read limit if that is higher. The 10,000/min announced on October 20 could not be reached because of the per-IP limit above; 3,000 is the ceiling actually in force.
- A
429 rate_limit_exceededfrom a per-key bucket names it:bucket(read,tradingorbuy_orders),limit_per_minandretry_after_sec.Retry-Afteris now the seconds left until the window restarts (1–60) instead of a flat 60 — here and onsnapshot_rate_limited, the P2P write limit and the Instant Sell quote limit. 429 too_many_streamscarriesRetry-After: 90,max_connectionsandretry_after_sec: a stream that dropped without closing frees its slot within 90 seconds.409 item_unavailableon CS2 buys names the ids we have no record of inunknown_ids: not a listing id (a Steam asset id is the usual mix-up), or a listing gone from the catalogue for over an hour.unavailable_idsstill lists every missing id.
New features
Manual delivery is flagged on every orderOrders now carrydelivery_type (instant | manual) and delivery_deadline_at in GET /v1/orders, GET /v1/orders/info, GET /v1/orders/{id} and the order.updated webhook, P2P rows included. A manual order is sent by a person and can take up to 12 hours, so it may sit in delivering with no trade offer that long. delivery_deadline_at is set while the order is pending or delivering; if the seller misses it, the order is cancelled and refunded automatically.Cancel an undelivered order: POST /v1/orders/{id}/cancelPOST /v1/orders/{id}/cancel calls off an order nobody has shipped yet and refunds it to your balance. It opens 31 minutes after purchase for manual delivery and 15 minutes for everything else; earlier calls answer 425 too_early with retry_after_sec. Player (P2P) lots cannot be cancelled here.New features
One item’s whole book, live:GET /v1/listings/depthGET /v1/listings/depth takes a market_hash_name, refreshes that item’s book from the upstream market first (your min_float / max_float, phase and delivery are passed through), then returns every lot we hold for the name, cheapest first, in the same Listing shape as /v1/listings. Every id goes straight into POST /v1/orders.refreshon the response says whether the live refresh happened (ok) and, if not, why (budget,timeout,unavailable,delivery). Without it you still get the book as of its last sighting.- The shelf itself is now far deeper: per-asset stock from our Chinese supply is kept at up to 1,000 lots per item (was 30) and floats are filled in continuously, so
/v1/listings?name=, the snapshot and the stream carry the whole book, not the cheapest few. /v1/listingsgained adoppler_phasefilter (exact phase, as the response carries it).
GET /v1/listings/snapshot.ndjson.gzGET /v1/listings/snapshot.ndjson.gz is every buyable listing as gzipped NDJSON, one Listing per line in the exact shape /v1/listings returns. Download it once, then open /v1/listings/stream with last_event_id set to the file’s X-Stream-Position header and your copy stays current from there. This replaces walking the catalogue page by page.- Rebuilt every 10 minutes while anyone is downloading it.
ETag/Last-ModifiedwithIf-None-Match/If-Modified-Sinceanswer304when nothing changed. - Same one-listing-per-physical-skin rule as
/v1/listings, same negotiated discount applied. - Six full downloads per hour per key; a
304is free.
Changes
- Trading calls (
POST /v1/orders,POST /v1/market/buy,GET /v1/orders*,POST /v1/orders/{id}/claim,GET /v1/balance) now count against their own per-key budget of 10,000 requests/min instead of the 100/min read budget, so paging the catalogue can no longer block a buy or an order-status poll. See Rate limits. - The stream keeps about two hours of history (was ~15 minutes at current volume) and a reconnect replays the whole gap, however long, instead of the first 5,000 events. The
resyncevent now names the snapshot as the way back in.
Fixes
price_usdon/v1/listings,/v1/listings/depth, the stream and the snapshot is now exactly what the buy charges. Lots from about $100 whose price we lift toward the item’s next-cheapest listing were shown at the pre-lift price, so sending that number asmax_price_usdwas refused withprice_movedevery time. If you sawprice_movedrepeat on the same item, this was it.
New features
Buy Dota 2 and Rust with delivery to any trade URLPOST /v1/market/buy now takes appId in the body, like POST /v1/orders: 570 for Dota 2, 252490 for Rust. Leave it out and the endpoint buys CS2, as before.- Payment and delivery work as they do for CS2: settled balance only, and the skins go to the trade URL built from
partner+token. - A Dota 2 or Rust order takes up to 10 ids. More answers
400 too_many_items. - Dota 2 and Rust orders are queued, so every skin answers
pendingfirst. Follow the order withGET /v1/orders/infoor theorder.updatedwebhook.
New features
Instant Sell for Dota 2 and Rust, and sales across several offersPOST /v1/sell/quotesandPOST /v1/sell/orderstakeapp_id:730CS2 (the default),570Dota 2,252490Rust. A game your key cannot sell answers422 unsupported_gamewithsupported_app_ids. Quote items and orders carryapp_id.- New
splitflag. Leave itfalseand the sale ships as one trade offer, to the market that leaves the seller the most value. Set ittrueand every item goes to the market that pays the most for it, which can mean several offers. Quote items carryoffer_group(a,b, and so on) so you know how many offers to expect. - Orders now carry
trade_offer_ids, anoffer_groupon each offer and astatuson each item. Thesell.updatedwebhook sends the same object. - Credit timing per game: CS2 about 7 days after the seller accepts, Dota 2 7 days after the seller accepts, Rust as soon as the market confirms. See when the money arrives.
min_total_usdis now checked against the total the order actually books, after the items are grouped into offers. Below it, the order is refused with409 price_drift.- The Instant Sell endpoints have their own pages in the API reference, including
GET /v1/sell/orderswith list mode and pagination.
sell.updated, withdrawal.updated and deposit.updated with their payloads.New features
Dota 2 and Rust over the same API — choose the game withappIdCSBoard now sells Dota 2 and Rust items as well as CS2, and the API reaches them through one new parameter. Pass Steam’s appId — 570 for Dota 2, 252490 for Rust — and the endpoint answers for that game. Leave it out and you get CS2, byte for byte as before.appIdworks onGET /v1/listings,GET /v1/pricesandGET /v1/listings/availabilityas a query parameter, and onPOST /v1/ordersas a body field.- Dota 2 and Rust rows have the same keys as CS2 rows. The CS2-only fields are
nullandstickersis[], so one parser reads all three games. - CS2-only filters (
wear,min_float,max_float,stat_trak,souvenir,available_after,sort=newest) sent with another game answer400 invalid_paraminstead of being silently ignored. - An
appIdyour key cannot use answers400 unsupported_gamewithsupported_app_ids. New endpointGET /v1/gameslists the games your key can read and whether it can buy there. - Dota 2 and Rust orders answer
delivery: "pending"andexpected_minutes: null.
POST /v1/market/buy stay CS2 only.A keyless price feed: GET /v1/public/pricesGET /v1/public/prices returns one cheapest ask per market_hash_name for CS2, Dota 2 or Rust. It needs no API key and no balance. CS2 Doppler phases are merged into Steam’s base name, so every key matches a real Steam name. The feed is rebuilt once a minute and limited to 1 request per minute per IP.List your own skins on the P2P market from the API — freeFive new endpoints let a key put its owner’s CS2 items up for sale on the CSBoard P2P market. No balance requirement and no trading permission — any valid key works.GET /v1/p2p/inventory— your items, which can be listed now, and why the others cannot.POST /v1/p2p/listings— publish one item or up to 50;Idempotency-Keyrequired.GET /v1/p2p/listings,PATCH /v1/p2p/listings/{id}andDELETE /v1/p2p/listings/{id}— see, reprice and remove them.
422 price_below_market. Over a key it cannot be confirmed past, so a leaked key cannot dump your inventory. Writes are limited to 30 per minute per key. The P2P listing guide walks through the whole flow.Fixes
Purchases that time out now answer504, not 500. When a marketplace stops responding mid-purchase the outcome is genuinely unknown — the buy may still have completed. POST /market/buy and POST /orders now return 504 upstream_timeout for that case and say so explicitly.Crucially, the idempotency claim is held instead of released. Re-sending the identical custom_id / Idempotency-Key replays the original outcome rather than buying a second time; a retry sent while the original is still unresolved returns 409 idempotency_in_progress. Poll the order list before treating a 504 as a failure. Deterministic errors (price moved, insufficient balance, item unavailable) are unchanged.Lost concurrency races answer 409 concurrent_update. When two orders touch the same items at the same instant the loser’s transaction is rolled back whole — nothing charged, nothing reserved. That used to surface as 500; it is now a 409 with Retry-After, because retrying is exactly the right move.New features
Stop polling for new listings — subscribe to themUntil today the only way to notice new inventory was to pollGET /v1/listings harder, and that cost does not divide across clients: ten integrators at 300 requests/minute is ten separate deep scans of the same table returning almost identical rows. Worse, the thing everyone was polling could not answer the question — sort=newest ordered by when the row was created, so items returning to the catalogue from cancelled orders, expired trade offers and released holds never surfaced at the top, no matter how fast anyone polled.Three additions, all backward compatible:- New endpoint
GET /v1/listings/stream— Server-Sent Events. One connection replaces any polling loop, and it emitsgoneevents as well asnewones. That second half is what polling cannot express: a poller only discovers an item sold by trying to buy it and failing. Reconnects are gap-free via the standardLast-Event-IDheader, including across our deploys. - New query parameter
available_afteronGET /v1/listings— turns a poll into a delta read. The usual response is an empty page. Overlap your watermark by ~60 seconds and dedupe byid; the parameter docs explain why that overlap is not optional. - New field
listed_aton everyListing— when the item entered the catalogue, which is whatsort=newestnow orders by. Re-listings surface alongside genuinely new inventory.
sort=newest keeps its name and its ordering direction, it simply stopped hiding re-listings.Fixes
Corrected rate limits in the docsThe published limits were wrong in the direction that costs you throughput: pointed reads are 100 requests/minute, not 30, andPOST /v1/orders is 250/minute, not 30. If you sized your client against the old numbers, you have more headroom than you thought — and if you were about to ask for a raise, try available_after or the stream first.New features
Refunds are now explicit — no more guessingAn order that endscancelled or failed has always been refunded in full, usually within seconds. The API just never said so, which left clients rendering “refund pending” against orders that were already settled. Two additions fix that:- Every order — in
GET /v1/orders,GET /v1/orders/{id}andGET /v1/orders/info— now carries arefundobject:amount_usd,refunded_at, andpartial(true when only one leg of a multi-item order came back). It isnullwhen nothing was refunded. - New endpoint
GET /v1/transactions— your full balance ledger, keyset paginated:purchase,refund,sale,deposit,withdrawal,bonus,fee,adjustment. A purchase and the refund that reversed it share the sameorder_id, so an end-of-day reconciliation is one pass over this feed.
status vocabulary is unchanged, so existing integrations keep working untouched.New features
Instant Sell API (invite-only)Sell CS2 skins programmatically through the CSBoard bot network. Quote a Steam inventory, create a sell order, and funds settle to your CSBoard balance once Steam’s trade-protection hold clears (~8 days).- Quote any Steam inventory with
POST /v1/sell/quotes— every eligible item comes back with the exact USD amount you’ll be credited. - Create orders with
POST /v1/sell/orders, with optionalmin_total_usdprice-drift protection andexternal_ididempotency. - Track lifecycle (
pending→offer_sent→received→completed) and settled amounts withGET /v1/sell/orders/:id. Large baskets can split across up to 3 bots. - Every offer exposes the sending bot’s Steam profile and
trade_offer_idso you can render anti-impersonation checks to sellers. - Access is granted per-account — call
GET /v1/sell/statusto check, and contact support to request access.
Fixes
Precise 402 error codes onPOST /v1/market/buyThe endpoint now distinguishes the two out-of-funds cases instead of always returning insufficient_settled_balance:insufficient_balance— the account balance is simply lower than the order total. Top up and retry.insufficient_settled_balance— the balance covers the total, but part of it is still inside the reversal window and cannot fund external delivery yet.
balance_usd alongside required_usd and settled_usd, so a bot can tell the cases apart programmatically. If your integration matched on the insufficient_settled_balance code for generic low-funds handling, match on HTTP 402 instead.Held orders also gained a small post-unlock grace before claimable flips to true — claiming at the exact unlock instant previously failed on the marketplace side.New features
Claim held orders over the APIOrders that land under a marketplace hold (status: "hold" with a hold_until timestamp) can now be released programmatically. Once hold_until passes, call POST /v1/orders/:id/claim to release the order. After a successful claim the marketplace sends a Steam trade offer your bot must accept within ~15 minutes. Some held orders deliver automatically, in which case the call is a harmless no-op.autoclaim account settingSet autoclaim: true on POST /v1/orders or POST /v1/market/buy and every held order is released automatically the moment its hold clears — no per-order call required. The flag is sticky at the account level.Exact name filter on listings and pricesPass a name query parameter to GET /v1/listings or GET /v1/prices to fetch a single item by its exact market_hash_name (case-insensitive). Unlike search, which does fuzzy word matching and can collide on overlapping titles (for example, Spectrum Case vs Spectrum 2 Case), name returns only the item you asked for. When both parameters are supplied, name takes precedence — no more post-filtering the response.See Market Data for usage examples.