API

Buying items

Find listings and buy them in a single atomic call.

Open raw markdownFor pasting into AI assistants

Buying is one call. You name the listings you want and the most you're willing to pay for each, and the order either fills completely or not at all.

There is no cart. v1's CreateCart / AddItems / PurchaseWithWallet dance is replaced by a single request, and what you buy lands in your backpack, ready to sell again or withdraw to Steam.

1. Find something to buy

GET /public/v1/listings returns active listings, newest first, filtered by game:

curl "https://api.cs.deals/public/v1/listings?page=1&limit=500&app_id=730" \
  -H "Authorization: Bearer csd_..."

app_id is 730 for CS2, 252490 for Rust, 570 for Dota 2, 440 for TF2. Each listing carries an id (the listing_id you pass when buying), a price in cents, and an amount, which is always 1: every listing holds exactly one copy, so an item with many copies for sale appears as many listings. Rows also include the full per-game item fields (cs_paint_wear, cs_paint_seed, cs_stickers, cs_inspect_link, and the Rust/Dota/TF2 equivalents; other games' fields are null), so float, pattern, and sticker-craft filtering happens client-side. app_id is the only server-side filter. The WebSocket listing.created event carries the same fields, so a bot can judge a new listing the moment it appears.

To re-check one listing you already know about, rather than paging the whole book, use GET /public/v1/listings/{id}:

curl "https://api.cs.deals/public/v1/listings/123456" \
  -H "Authorization: Bearer csd_..."

It returns the same shape as a row from the list endpoint. Once the listing is sold or delisted it returns 404 LISTING_NOT_FOUND, which is the cheapest way to confirm a listing is gone before you try to buy it.

For a market-wide price snapshot rather than individual listings, use GET /public/v1/prices/all: every priced item in one cached response, which is much cheaper than paging through GET /public/v1/prices or pulling the whole book. Each row carries lowest_listing_price, the cheapest live listing here for that item, next to the Steam-derived market_price, plus stock (how many units are listed for it) and listing_count (how many listings those units sit across). Every listing holds one unit, so the two are equal.

2. Buy it

POST /public/v1/purchase takes the items directly:

curl -X POST https://api.cs.deals/public/v1/purchase \
  -H "Authorization: Bearer csd_..." \
  -H "Content-Type: application/json" \
  -d '{ "items": [{ "listing_id": 12345, "amount": 1, "max_price": 4250 }] }'

max_price is a ceiling, not an exact price. If the listing dropped to 4000 between your read and your write, the order fills and you are charged 4000. If it rose to 4300, the whole order fails with LISTING_PRICE_CHANGED and nothing is bought. Set it high to opt out of the check entirely.

Up to 50 lines per request, one line per listing, and each listing_id only once. Every listing holds one copy, so amount is always 1; to buy several copies of an item, send one line per listing. amount above 1 is rejected, normally with LISTING_OUT_OF_STOCK. A private listing needs its private_token on the line. The entire request is a single transaction: if any line fails, no money moves and no items change hands.

3. Read the response

You get back the order, the items you now own, and where they landed:

{
  "order_id": 9001,
  "created_at": "2026-08-06T12:00:00.000Z",
  "items": [
    {
      "order_item_id": 44,
      "app_id": 730,
      "market_hash_name": "AK-47 | Redline (Field-Tested)",
      "steam_asset_id": "39082153990",
      "price": 4000,
      "amount": 1
    }
  ]
}

Purchased items go straight to your on-site backpack (a linked account's purchases land in its master account's backpack). From there you either relist them or withdraw them to Steam.

Errors worth handling

CodeMeaning
LISTING_NOT_FOUNDNo such listing, or it was already sold or delisted: someone bought it first. A sold or delisted listing is removed, not marked. data.listing_ids says which.
LISTING_PRICE_CHANGEDA listing costs more than your max_price. data.listing_ids says which.
LISTING_OUT_OF_STOCKA line asked for amount above 1. data.listing_ids says which.
LISTING_NOT_ACTIVEThe listing exists but is not for sale.
INSUFFICIENT_BALANCEYour balance won't cover the order. Check GET /public/v1/user.
PURCHASE_OWN_LISTINGYou can't buy your own listing.
PURCHASE_FAMILY_LISTINGThe listing belongs to an account linked to yours (your master account or another of its linked accounts).
PURCHASE_RESTRICTEDPurchasing is restricted on your account.
PURCHASING_DISABLEDPurchasing is off site-wide. Retry later.

Losing a race is normal, not exceptional. A bot should expect LISTING_NOT_FOUND and LISTING_PRICE_CHANGED at a steady rate, drop those listings, and move on.

Your purchase history

GET /public/v1/orders pages through everything you have bought and sold, newest first, with side: "bought" on your purchases.

For the whole lot in one file, and for accounting, GET /public/v1/orders/export streams it as CSV:

curl "https://api.cs.deals/public/v1/orders/export?side=bought" \
  -H "Authorization: Bearer csd_..." -o purchases.csv

Add format=json for the same columns as JSON, app_id to narrow it to one game, and from/to as ISO dates for a single tax year. Money is in cents, unit_price per copy and total_value for the row.

What about the cart?

The website's checkout runs on separate cart endpoints that are not part of this API. The cart is per-account shared state: if you were to buy through it from a bot, you would also buy whatever is sitting in the cart from a browser session on the same account. Use POST /public/v1/purchase for anything automated.

Staying current

Polling /public/v1/listings finds new listings, but the WebSocket feed is faster and cheaper. It pushes listing.created, listing.price_changed, listing.amount_changed and listing.removed as they happen, each with a global sequence number. See the Overview for the connection and subscription protocol.

Between them those four cover a listing's whole life, so a client can hold a trustworthy local copy of the book: connect, buffer events, fetch GET /public/v1/book for the full active book plus the seq it is valid at, drop buffered events with seq at or below the snapshot's, and apply the rest. One request and one socket, no polling.

seq is an int64, strictly increasing but not contiguous, so gaps are normal. Events for the same listing can arrive slightly out of order, so apply one only when its seq beats the last seq you applied for that listing_id. Every payload is absolute state, which makes that check the whole of the ordering problem.

Rebuild rather than poll: the book is a large response and is rate-limited to six calls a minute. If you only want prices, not individual listings, GET /public/v1/prices/all is the cheap answer. The full protocol is in the Overview.