# Buying items

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](/docs/selling) or [withdraw to Steam](/docs/withdrawing).

## 1. Find something to buy [#1-find-something-to-buy]

[`GET /public/v1/listings`](/docs/reference/market-data/listings) returns active listings, newest
first, filtered by game:

```bash
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`, the number of copies available on that
listing. 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}`](/docs/reference/market-data/listing):

```bash
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`](/docs/reference/market-data/prices-all): every
priced item in one cached response, which is much cheaper than paging through
[`GET /public/v1/prices`](/docs/reference/market-data/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).

## 2. Buy it [#2-buy-it]

[`POST /public/v1/purchase`](/docs/reference/trading/purchase) takes the items directly:

```bash
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. The entire request is a
single transaction: if any line fails, no money moves and no items change
hands.

## 3. Read the response [#3-read-the-response]

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

```json
{
  "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. From there you either
relist them or [withdraw them to Steam](/docs/withdrawing).

## Errors worth handling [#errors-worth-handling]

| Code                    | Meaning                                                                                          |
| ----------------------- | ------------------------------------------------------------------------------------------------ |
| `LISTING_PRICE_CHANGED` | A listing costs more than your `max_price`. `data.listing_ids` says which.                       |
| `LISTING_OUT_OF_STOCK`  | Someone bought it first. `data.listing_ids` says which.                                          |
| `LISTING_NOT_ACTIVE`    | The listing was delisted or archived.                                                            |
| `LISTING_NOT_FOUND`     | No such listing id.                                                                              |
| `INSUFFICIENT_BALANCE`  | Your balance won't cover the order. Check [`GET /public/v1/user`](/docs/reference/account/user). |
| `PURCHASE_OWN_LISTING`  | You can't buy your own listing.                                                                  |
| `PURCHASING_DISABLED`   | Purchasing is off site-wide. Retry later.                                                        |

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

## Your purchase history [#your-purchase-history]

[`GET /public/v1/orders`](/docs/reference/account/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`](/docs/reference/account/orders-export) streams
it as CSV:

```bash
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? [#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 [#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](/docs/reference/market-data/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`](/docs/reference/market-data/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`](/docs/reference/market-data/prices-all) is the
cheap answer. The full protocol is in the
[Overview](/docs/reference/market-data/overview).
