# Quickstart

The CS Deals API gives you the marketplace: live listings and prices, buying,
withdrawing to Steam, and a WebSocket feed of market activity.

## Get an API key [#get-an-api-key]

Keys are created from **Settings → API** on the website. Creating and revoking
a key is deliberately browser-only: those routes reject requests that don't
come from the site, so a leaked key can never mint another one.

Two things are required before a key is issued:

* **A linked Discord or Telegram account**, so we have a way to reach you.
  Link one from **Settings → Connected Accounts**.
* **A short description of what you'll use the key for.** Keep it accurate;
  it's what we read first if your traffic ever looks unusual.

<Callout type="warn">
  Providing a fake Discord/Telegram will lead to API termination in the event we
  need to reach out and verify the legitimacy of your requests.
</Callout>

The key is shown **once**. Store it somewhere safe.

## Authenticate [#authenticate]

Send it as a bearer token on every request:

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

`GET /public/v1` is the only route that works without a key. To confirm a key
is live, call [`GET /auth/api-key`](/docs/reference/account/api-key).

<Callout type="warn">
  An API key carries your full account authority, including buying and
  withdrawing. Treat it like a password: never ship it in client-side code, and
  revoke it from Settings the moment it leaks.
</Callout>

## Money is always integer cents [#money-is-always-integer-cents]

Every price, balance and amount in this API is an integer in cents. `4250`
means $42.50. There are no floats and no currency codes to pass around. If
you are porting from the v1 API, this is the single most common source of bugs.

Field names are `snake_case` everywhere. CS Deals ids are integers; external
Steam identifiers are strings and always carry a `steam_` prefix
(`steam_asset_id`, `steam_offer_id`). Timestamps are ISO 8601 strings, on
REST and WebSocket alike.

## Errors [#errors]

Failures return a JSON body with a stable machine-readable code:

```json
{ "error": "LISTING_PRICE_CHANGED", "data": { "listing_ids": [12345] } }
```

Branch on `error`, never on the HTTP status or the message text. `data` is
present when the error carries detail: which listings were out of stock, which
were too expensive, and so on.

## Rate limits [#rate-limits]

Limits are per endpoint, counted per minute:

| Endpoint                   | Limit |
| -------------------------- | ----- |
| `GET /public/v1/listings`  | 60    |
| `GET /public/v1/book`      | 6     |
| `GET /public/v1/prices`    | 60    |
| `GET /public/v1/sales`     | 60    |
| `POST /public/v1/purchase` | 30    |
| `GET /public/v1/user`      | 60    |
| `GET /public/v1/orders`    | 60    |
| `GET /public/v1/backpack`  | 30    |
| `GET /public/v1/trades`    | 60    |

`POST /public/v1/withdraw` is not rate-limited. Exceeding a limit returns
`RATE_LIMITED` with HTTP 429. Back off and retry.

## Next [#next]

The guides follow the life of an item on the platform:

* [Buying items](/docs/buying): find listings and place an order
* [Selling items](/docs/selling): list from your backpack, reprice, delist
* [Withdrawing to Steam](/docs/withdrawing): get your items out
* [Migrating from the v1 API](/docs/migrating-from-v1): endpoint-by-endpoint mapping

Bought items sit in your **backpack** until you either sell them again or
withdraw them to Steam. There is no cart: an order is one call that fills
completely or not at all.

---

# 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`.

## 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).

---

# Withdrawing to Steam

Items you buy land in your on-site backpack. Withdrawing sends them to Steam as
a trade offer from one of our bots.

## Before your first withdrawal [#before-your-first-withdrawal]

Your account needs a **Steam trade URL** set, on the account the items are going
to. The API rejects a trade URL belonging to a different Steam account, so this
is a one-time setup step on the website.

Without it you get `TRADE_URL_NOT_SET` or `STEAM_ID_NOT_SET`.

## 1. List your backpack [#1-list-your-backpack]

[`GET /public/v1/backpack`](/docs/reference/trading/backpack) returns what you own, paginated:

```bash
curl "https://api.cs.deals/public/v1/backpack?page=1&limit=50" \
  -H "Authorization: Bearer csd_..."
```

Each row has an `id`, the **backpack item id**, which is what you withdraw
with. It is not the listing id and not the Steam asset id.

## 2. Request the withdrawal [#2-request-the-withdrawal]

[`POST /public/v1/withdraw`](/docs/reference/trading/withdraw):

```bash
curl -X POST https://api.cs.deals/public/v1/withdraw \
  -H "Authorization: Bearer csd_..." \
  -H "Content-Type: application/json" \
  -d '{ "items": [{ "id": 77, "amount": 1 }] }'
```

Items are held by different bots, so one request can become several trade
offers, one per bot. That's normal and invisible apart from receiving more
than one offer. The response's `withdraw_ids` carries one id per offer
created.

## 3. Accept the offer on Steam [#3-accept-the-offer-on-steam]

[`GET /public/v1/trades`](/docs/reference/trading/trades) tracks them. Each trade carries a
`steam_offer_id`, a `status` and its items. The offer arrives from a CS Deals bot; accept it in
Steam as you would any trade.

If you decline or let an offer expire, the items go back to your backpack
automatically.

## Limits [#limits]

| Limit                      | Value                           |
| -------------------------- | ------------------------------- |
| Items per withdrawal       | 50                              |
| Concurrent active trades   | 15                              |
| Value per withdrawal       | your tier's per-transaction cap |
| Value per rolling 24 hours | your tier's daily cap           |

Withdrawal value caps are set per account by tier. Crypto, bank and skin
withdrawals all draw from the same 24-hour bucket. Exceeding them returns
`WITHDRAW_LIMIT_EXCEEDED` or `WITHDRAW_DAILY_LIMIT_EXCEEDED`, both with the
limit in `data`; the daily one also includes your current usage as `used`. `WITHDRAW_DISABLED` means withdrawals
are not enabled on the account at all. Contact support.

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

| Code                        | Meaning                                                                |
| --------------------------- | ---------------------------------------------------------------------- |
| `ITEM_NOT_OWNED`            | A backpack item id isn't yours, or is already withdrawing.             |
| `INSUFFICIENT_ITEM_AMOUNT`  | You asked for more copies than you hold.                               |
| `ITEM_TRADE_LOCKED`         | Still inside Steam's trade hold.                                       |
| `ACTIVE_TRADE_LIMIT`        | 15 trades already in flight. Wait for one to settle.                   |
| `WITHDRAW_ITEM_LIMIT`       | More than 50 items in the request. Split it into smaller withdrawals.  |
| `BOT_UNAVAILABLE`           | The holding bot is temporarily unavailable. `data.itemIds` says which. |
| `TRADING_WITHDRAW_DISABLED` | Withdrawals are paused site-wide. Retry later.                         |

---

# Migrating from the v1 API

The legacy API (`/ICart/…`, `/ISales/…`, `/IBalance/…`) is replaced by this one.
This page maps what you call today onto what you should call now.

Nothing here is a like-for-like rename: the new API is REST-shaped, integer-only
and stateless where the old one was RPC-shaped, float-based and cart-stateful.
Read the three breaking changes first. They affect every request you make.

## Three things that change everywhere [#three-things-that-change-everywhere]

### 1. Authentication [#1-authentication]

v1 used HTTP Basic with the key as the username and an empty password:

```
Authorization: Basic base64(API_KEY:)
```

Now it is a bearer token:

```
Authorization: Bearer csd_your_key_here
```

Your v1 key does not carry over. Generate a new one from **Settings → API**.

### 2. Money is integer cents, everywhere [#2-money-is-integer-cents-everywhere]

v1 sent money as floats and made you declare a currency per field:
`data_currency_iso`, `payment_currency_iso`, `total_currency_iso`.

This API has one representation: **integers in cents**. `42.50` becomes `4250`.
There is no currency parameter to pass, anywhere. If you keep one float in your
port, you will eventually place an order off by a factor of 100. Strip them at
the boundary.

### 3. Buying no longer uses a cart [#3-buying-no-longer-uses-a-cart]

v1's cart flow (`CreateCart` → `AddItems` → `PurchaseWithWallet`) is gone.
v1 also allowed passing `items` straight to `PurchaseWithWallet`, and that is
the shape we kept:

```json
POST /public/v1/purchase
{ "items": [{ "listing_id": 12345, "amount": 1, "max_price": 4250 }] }
```

`max_price` behaves like v1's per-item `price`: a ceiling, not an exact match,
settable high to disable the check. The order is atomic: every line fills or
none do.

The website's cart endpoints still exist but are not part of this API. They
share state with the account's browser session. Don't automate against them.

## Endpoint mapping [#endpoint-mapping]

### Buying [#buying]

| v1                                           | Now                                                                                                       |
| -------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| `POST /ICart/CreateCart/v1`                  | *(gone, no cart to create)*                                                                               |
| `POST /ICart/AddItems/v1`                    | *(gone, pass items to purchase)*                                                                          |
| `POST /ICart/GetCart/v1`                     | *(gone)*                                                                                                  |
| `POST /ICart/ClearCart/v1`                   | *(gone)*                                                                                                  |
| `POST /IAdyenOrder/PurchaseWithWallet/v1`    | [`POST /public/v1/purchase`](/docs/reference/trading/purchase)                                            |
| `POST /IAdyenOrder/PurchaseWithCryptoUSD/v1` | [`POST /public/v1/purchase`](/docs/reference/trading/purchase) (one balance now, no per-currency wallets) |

### Market data [#market-data]

| v1                                  | Now                                                                                                                                  |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `POST /IPricing/GetLowestPrices/v1` | [`GET /public/v1/prices/all`](/docs/reference/market-data/prices-all) (the `lowest_listing_price` field; one cached blob, as before) |
| `POST /IPricing/GetSalesHistory/v1` | [`GET /public/v1/sales`](/docs/reference/market-data/sales) (recent sales, filterable by item)                                       |
| *(none)*                            | [`GET /public/v1/listings`](/docs/reference/market-data/listings) (individual live listings)                                         |
| *(none)*                            | [`GET /public/v1/book`](/docs/reference/market-data/book) (full book snapshot with sequence number)                                  |
| *(none)*                            | WebSocket feed (push instead of polling), see [Overview](/docs/reference/market-data/overview)                                       |

### Inventory and withdrawing [#inventory-and-withdrawing]

| v1                                      | Now                                                                                                                              |
| --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `GET /IInventory/GetOnSiteInventory/v1` | [`GET /public/v1/backpack`](/docs/reference/trading/backpack)                                                                    |
| `POST /IInventory/WithdrawSteam/v1`     | [`POST /public/v1/withdraw`](/docs/reference/trading/withdraw) (same `{ id, amount }` item shape)                                |
| `POST /IInventory/DepositSteam/v1`      | [`POST /public/v1/sell`](/docs/reference/selling/sell) covers deposit-and-list; depositing without listing is still website-only |
| `GET /ITrades/GetActiveTrades/v1`       | [`GET /public/v1/trades`](/docs/reference/trading/trades) (filter by `status`)                                                   |
| `GET /ITrades/GetTradeHistory/v1`       | [`GET /public/v1/trades`](/docs/reference/trading/trades) (omit the `status` filter)                                             |
| `POST /ITrades/AcceptCancelTrade/v1`    | *(no equivalent, accept offers in Steam)*                                                                                        |
| `POST /IUser/SetSteamTradeToken/v1`     | *(website only, Settings)*                                                                                                       |

### Account [#account]

| v1                                    | Now                                                                                                            |
| ------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| `GET /IBalance/GetBalance/v1`, `/v2`  | [`GET /public/v1/user`](/docs/reference/account/user) (one balance, in cents)                                  |
| `POST /IBalance/GetTransactions/v1`   | [`GET /public/v1/transactions`](/docs/reference/account/transactions) (signed cents, with the running balance) |
| `POST /ISales/GetSoldItems/v1`, `/v2` | [`GET /public/v1/orders`](/docs/reference/account/orders) (rows with `side: "sold"`)                           |

### Selling [#selling]

| v1                                                 | Now                                                                                                                                                                       |
| -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `POST /ISales/ListItems/v1`, `/v2` (Steam items)   | [`POST /public/v1/sell`](/docs/reference/selling/sell) (tokens from [`GET /public/v1/steam-inventory`](/docs/reference/selling/steam-inventory); we send the trade offer) |
| `POST /ISales/ListItems/v1`, `/v2` (on-site items) | [`POST /public/v1/list`](/docs/reference/selling/list) (backpack item ids, price in cents)                                                                                |
| `POST /ISales/EditItems/v2`, `/v3`                 | [`PATCH /public/v1/list`](/docs/reference/selling/edit-listing) (one listing per call)                                                                                    |
| `POST /ISales/GetActiveListings/v2`, `/v3`         | [`GET /public/v1/my-listings`](/docs/reference/selling/my-listings) (filter by `status`)                                                                                  |
| `POST /ISales/ReturnItems/v1`                      | [`POST /public/v1/delist`](/docs/reference/selling/delist)                                                                                                                |
| `GET /ISales/GetActiveListingsValue/v1`            | [`GET /public/v1/my-listings/value`](/docs/reference/selling/my-listings-value)                                                                                           |

### Cashout, exports, screenshots [#cashout-exports-screenshots]

| v1                                            | Now                                                                                                             |
| --------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `POST /ICashout/RequestBitcoin/v1`, `/v2`     | *(website only)*                                                                                                |
| `GET /ICashout/GetBitcoinCashoutAddresses/v1` | *(website only)*                                                                                                |
| `POST /IExport/CreateExport/v1`               | [`GET /public/v1/orders/export`](/docs/reference/account/orders-export) (CSV or JSON, streamed, no job to poll) |
| `POST /IScreenshots/QueueScreenshots/v1`      | *(no equivalent yet)*                                                                                           |
| `POST /IPricing/GetSalesHistory/v1`           | [`GET /public/v1/sales`](/docs/reference/market-data/sales)                                                     |

## Gaps, stated plainly [#gaps-stated-plainly]

Cashouts and screenshots have no equivalent here yet, and depositing items
without listing them is still website-only.
v1 is switched off, so there is nothing to fall back to: until these land, do
them from the website. Everything else your bot does, selling included, can
move now.

Two differences worth knowing when you port your selling code: a listing is
created with backpack item ids from `GET /public/v1/backpack` rather than
Steam asset ids, and auto-decaying prices stay website-only, so
`PATCH /public/v1/list` takes a flat price in cents.

Tell us which gap blocks you; that's what drives the order we close them in.

## Suggested migration order [#suggested-migration-order]

1. **Swap authentication** and re-point your base URL. Everything else fails
   loudly until this is right.
2. **Strip currency handling**: delete the `*_currency_iso` parameters and
   convert your money handling to integer cents at the API boundary.
3. **Replace the cart flow** with a single `POST /public/v1/purchase`. Your
   per-item price ceiling carries over unchanged.
4. **Re-point inventory, selling and withdrawal** calls. Item ids are ours, not
   Steam's, and come from `GET /public/v1/backpack`.
5. **Replace price polling** with the WebSocket feed once the rest is stable.

---

# Selling items

There are two ways to sell, depending on where the item is right now:

* Still in **Steam**: [`POST /public/v1/sell`](/docs/reference/selling/sell).
  We send you a trade offer, and the item goes on the market when you accept.
* Already in your **backpack** on site:
  [`POST /public/v1/list`](/docs/reference/selling/list). No trade offer, it is
  listed immediately.

Both end at the same place, and the same edit, delist and reporting calls work
on whatever they produce.

## Selling from Steam [#selling-from-steam]

[`GET /public/v1/steam-inventory?app_id=730`](/docs/reference/selling/steam-inventory)
reads your live Steam inventory. Every row carries a `token`, a signed handle
for that item that expires after 30 minutes:

```bash
curl "https://api.cs.deals/public/v1/steam-inventory?app_id=730" \
  -H "Authorization: Bearer csd_..."
```

Hand those tokens straight to the sell call with a price per copy in cents:

```bash
curl -X POST "https://api.cs.deals/public/v1/sell" \
  -H "Authorization: Bearer csd_..." \
  -H "Content-Type: application/json" \
  -d '{ "listings": [{ "items": [{ "token": "eyJ...", "amount": 1 }], "price": 4250 }] }'
```

You get back `deposit_ids`. Each one appears as `deposit_id` on a
[`GET /public/v1/trades`](/docs/reference/trading/trades) row, which is where
you watch for the offer, its `steam_offer_id`, and whether it was accepted.
Nothing is listed until you accept the offer in Steam.

## Selling from your backpack [#selling-from-your-backpack]

Anything already in your backpack can be listed without a trade offer: items
you bought here, and items you deposited earlier. These calls all work on the
same **backpack item id** you would withdraw with.

### 1. Find what you can sell [#1-find-what-you-can-sell]

[`GET /public/v1/backpack`](/docs/reference/trading/backpack) returns what you
own:

```bash
curl "https://api.cs.deals/public/v1/backpack?page=1&limit=50" \
  -H "Authorization: Bearer csd_..."
```

The `id` on each row is the backpack item id. It is not the listing id and not
the Steam asset id. An item with `trade_locked_until` set can still be listed;
it just cannot leave the platform until the hold expires.

For a price to ask, [`GET /public/v1/prices/all`](/docs/reference/market-data/prices-all)
gives `market_price` and `recommended_price` for every item in one cached
response.

### 2. List it [#2-list-it]

[`POST /public/v1/list`](/docs/reference/selling/list) takes groups of items,
each with one price in cents:

```bash
curl -X POST "https://api.cs.deals/public/v1/list" \
  -H "Authorization: Bearer csd_..." \
  -H "Content-Type: application/json" \
  -d '{ "listings": [{ "items": [{ "id": 8891, "amount": 1 }], "price": 4250 }] }'
```

```json
{
  "listings": [
    {
      "id": 55120,
      "app_id": 730,
      "market_hash_name": "AK-47 | Redline (Field-Tested)",
      "price": 4250,
      "amount": 1,
      "commodity": false,
      "created_at": "2026-08-09T16:20:00.000Z"
    }
  ]
}
```

Each group becomes one listing, so a single call can put fifty different items
up at fifty different prices. Identical commodity items grouped together sell
as one stack of `amount`.

A commodity backpack row is a quantity, not a single copy, so the same `id` can
appear in as many groups as you like — that is how you get separate listings,
each with its own `listing_id`, for copies of the same item at the same price:

```bash
# three Cloth, three listings, all at 10c
curl -X POST "https://api.cs.deals/public/v1/list" \
  -H "Authorization: Bearer csd_..." \
  -H "Content-Type: application/json" \
  -d '{ "listings": [
        { "items": [{ "id": 8891, "amount": 1 }], "price": 10 },
        { "items": [{ "id": 8891, "amount": 1 }], "price": 10 },
        { "items": [{ "id": 8891, "amount": 1 }], "price": 10 }
      ] }'
```

The groups draw from one pool, so asking for more than the row's `amount` in
total fails with `INSUFFICIENT_ITEMS` and nothing is listed.

Keep the returned `id`. That is the `listing_id` every later call uses, and the
id buyers see in the public book.

### 3. Reprice or resize [#3-reprice-or-resize]

[`PATCH /public/v1/list`](/docs/reference/selling/edit-listing) changes a
listing. Send `price`, `amount`, or both:

```bash
curl -X PATCH "https://api.cs.deals/public/v1/list" \
  -H "Authorization: Bearer csd_..." \
  -H "Content-Type: application/json" \
  -d '{ "listing_id": 55120, "price": 3990 }'
```

Raising `amount` takes more of the same item from your backpack. Lowering it
returns the surplus to your backpack — unless you send `price` in the same
call, which is treated as a **partial reprice**:

```bash
# 10 Cloth listed at 10c; move one of them to 9c
curl -X PATCH "https://api.cs.deals/public/v1/list" \
  -H "Authorization: Bearer csd_..." \
  -H "Content-Type: application/json" \
  -d '{ "listing_id": 55120, "amount": 1, "price": 9 }'
```

That leaves listing `55120` holding 1 at 9c and puts the other 9 in a new
listing, still at 10c. Nothing goes back to your backpack, so repricing part of
a stack never takes the rest off sale. The response is the listing you edited;
pick up the new one from
[`GET /public/v1/my-listings`](/docs/reference/selling/my-listings).

Auto-decaying prices are website-only, so a bot that wants a decay curve
reprices on its own schedule.

#### Repricing in bulk [#repricing-in-bulk]

Send a `listings` array instead of a single body to edit up to 50 at once. The
per-listing rules are identical, including the partial reprice above:

```bash
curl -X PATCH "https://api.cs.deals/public/v1/list" \
  -H "Authorization: Bearer csd_..." \
  -H "Content-Type: application/json" \
  -d '{ "listings": [
        { "listing_id": 55120, "price": 3990 },
        { "listing_id": 55121, "price": 4250 }
      ] }'
```

Each listing is applied on its own, so one bad `listing_id` does not stop the
rest. You get a row per listing, in the order you sent them, carrying the same
error code the single-listing form would have returned:

```json
{
  "results": [
    { "listing_id": 55120, "ok": true, "error": null, "listing": { "id": 55120, "price": 3990 } },
    { "listing_id": 55121, "ok": false, "error": "LISTING_NOT_FOUND", "listing": null }
  ]
}
```

The rate limit counts calls, not listings, so a batch of 50 lifts the ceiling
from 30 edits a minute to 1,500.

### 4. Take it down [#4-take-it-down]

[`POST /public/v1/delist`](/docs/reference/selling/delist) removes a listing and
returns its items to your backpack:

```bash
curl -X POST "https://api.cs.deals/public/v1/delist" \
  -H "Authorization: Bearer csd_..." \
  -H "Content-Type: application/json" \
  -d '{ "listing_id": 55120 }'
```

Send `listing_ids` instead to remove up to 50 at once. As with bulk editing,
each is removed on its own and comes back in `results` with its own `ok` and
`error`:

```bash
curl -X POST "https://api.cs.deals/public/v1/delist" \
  -H "Authorization: Bearer csd_..." \
  -H "Content-Type: application/json" \
  -d '{ "listing_ids": [55120, 55121, 55122] }'
```

## Keeping track [#keeping-track]

[`GET /public/v1/my-listings`](/docs/reference/selling/my-listings) lists your
own listings, filterable by `status`:

* `ACTIVE` is on the market now
* `FILLED` sold out
* `DISABLED` was taken down

`available_amount` on each row is how many more copies of that commodity you
still hold in the backpack, which is what you can add to the listing.

Accounts have a ceiling on how much they can have listed at once. Past it,
listing fails with `LISTING_LIMIT_REACHED`. If you are running into it, talk
to support.

[`GET /public/v1/my-listings/value`](/docs/reference/selling/my-listings-value)
answers "what am I currently asking for, in total" in one call:

```json
{ "listing_count": 214, "item_count": 388, "total_value": 1049900 }
```

## When something sells [#when-something-sells]

A sale is not pushed to you as a private event. Two ways to see it:

* [`GET /public/v1/orders`](/docs/reference/account/orders) shows rows with
  `side: "sold"`.
* [`GET /public/v1/transactions`](/docs/reference/account/transactions) shows
  the money arriving, action `SALE`, with your running balance.

The public WebSocket feed also carries `listing.amount_changed` and
`listing.removed` for your listings, but those are public events about the book,
not a private notification: `new_amount: 0` means the listing sold out.

For books and tax season,
[`GET /public/v1/orders/export`](/docs/reference/account/orders-export) hands
back your whole history in one file, CSV by default:

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

Each row carries the commission in `fee` and what you actually kept in `net`.

---

# CS Deals API

Market data and trading for CS2, Rust, Dota 2 and TF2 skins. Prices and amounts are integers, prices in cents.

Version: v1

Base URL: `https://api.cs.deals`

## Authentication

- API key generated from your account settings on the website. Send as `Authorization: Bearer csd_...`.

## Overview

`GET /public/v1`

No authentication required.

CS Deals offers a public API for marketplace data and trading: REST endpoints
for listings, prices, sales, buying, withdrawing and account state, plus a
real-time WebSocket feed of listing activity with sequence numbers for
building a trustworthy local book.

### Authentication

Create a personal API key from **Settings → API** on the website (key
creation is browser-only), then authenticate with
`Authorization: Bearer csd_...` on every REST request. This info endpoint is
the only unauthenticated route.

### Conventions

Every field is `snake_case`. Prices, balances and amounts are integers
(prices in cents). CS Deals ids are integers; external Steam identifiers are
strings and always prefixed `steam_` (`steam_asset_id`,
`steam_offer_id`). Timestamps are ISO 8601 strings everywhere, REST and
WebSocket alike.

### REST endpoints

Every route, what it is for, and its rate limit in requests per minute. Limits
are per API key, per endpoint.

#### Market data

| Endpoint | What it does | Limit |
|----------|--------------|-------|
| [`GET /public/v1/prices/all`](/docs/reference/market-data/prices-all) | Every price in one cached response. Start here. | 30 |
| [`GET /public/v1/prices`](/docs/reference/market-data/prices) | The same prices, paginated. | 60 |
| [`GET /public/v1/listings`](/docs/reference/market-data/listings) | Live listings with full item detail, newest first. Pages of 500 or 1000. | 1/sec |
| [`GET /public/v1/book`](/docs/reference/market-data/book) | Snapshot of every active listing plus `seq`. For (re)syncing a local book, not polling. | 6 |
| [`GET /public/v1/sales`](/docs/reference/market-data/sales) | Recent sales, filterable by item. | 60 |

#### Buying and selling

| Endpoint | What it does | Limit |
|----------|--------------|-------|
| [`POST /public/v1/purchase`](/docs/reference/trading/purchase) | Buy listings atomically. No cart. | 30 |
| [`GET /public/v1/steam-inventory`](/docs/reference/selling/steam-inventory) | What is in your Steam inventory, with a token per item. | 5 |
| [`POST /public/v1/sell`](/docs/reference/selling/sell) | List straight from Steam; we send you a trade offer. | 30 |
| [`POST /public/v1/list`](/docs/reference/selling/list) | Put backpack items on the market. | 30 |
| [`PATCH /public/v1/list`](/docs/reference/selling/edit-listing) | Change a listing's price or amount, one or up to 50 at a time. | 30 |
| [`POST /public/v1/delist`](/docs/reference/selling/delist) | Take listings down, one or up to 50 at a time; items return to your backpack. | 30 |
| [`GET /public/v1/my-listings`](/docs/reference/selling/my-listings) | Your own listings, filterable by status. | 60 |
| [`GET /public/v1/my-listings/value`](/docs/reference/selling/my-listings-value) | What your active listings are worth. | 30 |

#### Items and trades

| Endpoint | What it does | Limit |
|----------|--------------|-------|
| [`GET /public/v1/backpack`](/docs/reference/trading/backpack) | Your on-site items, listable and withdrawable by `id`. | 30 |
| [`POST /public/v1/withdraw`](/docs/reference/trading/withdraw) | Send backpack items to Steam as trade offers. | not limited |
| [`GET /public/v1/trades`](/docs/reference/trading/trades) | Your Steam trades, filterable by status. Pages of 500 or 1000. | 1/sec |

#### Account

| Endpoint | What it does | Limit |
|----------|--------------|-------|
| [`GET /public/v1/user`](/docs/reference/account/user) | Your id, steam_id, name and balance. | 60 |
| [`GET /public/v1/orders`](/docs/reference/account/orders) | Your bought and sold order items. | 30 |
| [`GET /public/v1/orders/export`](/docs/reference/account/orders-export) | Your whole order history as CSV or JSON, in one download. | 5 |
| [`GET /public/v1/transactions`](/docs/reference/account/transactions) | Every movement of your balance, with the running balance. | 30 |

`GET /public/v1/listings` and `GET /public/v1/trades` are metered per second
rather than per minute: one request a second, each returning up to 1000 rows.
Their `limit` accepts only `500` or `1000`; every other paginated route
keeps the usual sizes.

Rate-limited responses carry their budget:

| Header | Meaning |
|--------|---------|
| `X-RateLimit-Limit` | Requests allowed in the window |
| `X-RateLimit-Remaining` | Requests left in the current window |
| `X-RateLimit-Reset` | Unix seconds when the window resets |

Exceeding a limit returns `429 RATE_LIMITED` with a `Retry-After` header in
seconds. Unauthenticated requests are limited by IP instead.

### WebSocket feed

Connect to `wss://api.cs.deals/public/v1/ws` with your API key (Bearer
header or `?api_key=` query parameter). The scheme is `wss://`, not
`https://`. Connections without a valid key are closed with code `4401`.
Each user may hold at most 3 concurrent
connections; further attempts are closed with code `4429`.

```js
// Node, header auth
import WebSocket from "ws";

const ws = new WebSocket("wss://api.cs.deals/public/v1/ws", {
    headers: { Authorization: `Bearer ${process.env.CSDEALS_API_KEY}` },
});

ws.on("message", (data) => console.log(JSON.parse(data.toString())));
ws.on("close", (code, reason) => console.log("closed", code, reason.toString()));
```

```js
// Browser: no headers on a WebSocket, so authenticate by query parameter
const ws = new WebSocket("wss://api.cs.deals/public/v1/ws?api_key=csd_...");
ws.onmessage = (event) => console.log(JSON.parse(event.data));
```

The first frame is `{ "event": "connected", "data": null, "ts": ... }`.

Every event is a JSON envelope:
`{ "event": string, "data": object, "seq": number, "ts": string }`.
`ts` is the server timestamp, ISO 8601. Control messages (`connected`,
`subscribed`, `error`) carry no `seq`.

`seq` is a **signed 64-bit integer** (`int64`), global across all listing
events and shared with the `seq` returned by `GET /public/v1/book`. Decode it
into a 64-bit type, not a 32-bit one. It is strictly increasing but **not
contiguous**: gaps are normal and never mean you missed an event.

Because the API runs on several processes, two events for the *same* listing
can reach you slightly out of order. Every payload is absolute state, so the
rule is one line: **apply an event only if its `seq` is higher than the last
`seq` you applied for that `listing_id`**, and ignore it otherwise.

| Event | Payload | Emitted when |
|-------|---------|--------------|
| `listing.created` | `{ listing_id, app_id, market_hash_name, price, amount, commodity, created_at, ...item fields }` | A new listing goes live |
| `listing.price_changed` | `{ listing_id, app_id, price_before, price_after }` | A seller edits a listing's price |
| `listing.amount_changed` | `{ listing_id, app_id, new_amount }` | Stock changes after a purchase (`new_amount: 0` means sold out) |
| `listing.removed` | `{ listing_id, app_id }` | A seller delists, or the listing otherwise leaves the book |

`listing.created` carries the same per-game item fields as
`GET /public/v1/listings` rows (float, paint seed, stickers, and so on), so
clients can filter interesting listings the moment they appear without a
follow-up request.

#### Building a local book

Every event payload is absolute state (full listing on create, the new price,
the new amount), so applying an event twice is harmless. That makes the
sync protocol simple:

1. Connect to the WebSocket and start buffering events.
2. `GET /public/v1/book`: you receive `{ seq, listings }`.
3. Load the listings, drop buffered events with `seq <=` the snapshot's
   `seq`, apply the rest in order, then keep applying live events.

A `price_changed` or `amount_changed` for a listing you don't hold can be
ignored. If the socket drops, reconnect and repeat from step 1. One request
and one socket rebuilds the whole state.

#### Filtering

By default a connection receives every event. Send a subscribe message to
narrow the feed. Each message **replaces** the connection's filter:

```json
{ "op": "subscribe", "events": ["listing.created"], "app_ids": [730] }
```

Omitting `events` or `app_ids` (or sending an empty array) removes the
filter for that dimension. The server confirms with
`{ "event": "subscribed", "data": { events, app_ids } }`, where an
unfiltered dimension is reported as the string `"all"` instead of an array;
malformed messages get `{ "event": "error" }` and leave the filter
unchanged.

The event set will grow over time; unknown events should be ignored by
clients for forward compatibility.

**Response 200**: Public API metadata

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "version": {
      "type": "string"
    },
    "websocket": {
      "type": "object",
      "properties": {
        "path": {
          "type": "string"
        },
        "auth": {
          "type": "string"
        }
      },
      "required": [
        "path",
        "auth"
      ],
      "additionalProperties": false
    },
    "events": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "rest": {
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  },
  "required": [
    "version",
    "websocket",
    "events",
    "rest"
  ],
  "additionalProperties": false
}
```

## Listings

`GET /public/v1/listings`

Requires `Authorization: Bearer csd_...`.

Paginated active listings, newest first, each with the full per-game item fields (`cs_paint_wear`, `cs_paint_seed`, `cs_stickers`, `cs_inspect_link`, and the Rust/Dota/TF2 equivalents); fields for other games are `null`. Filter by `app_id` (730 = CS2, 252490 = Rust, 570 = Dota 2, 440 = TF2); anything finer (float ranges, stickers) is meant to be filtered client-side. Prices are integers in cents. Pass `cursor` (a listing id) to page stably through a churning book: each response's `next_cursor` is the value for the next request, `null` when exhausted. `page` remains available for offset pagination. `limit` accepts only `500` or `1000`, and the route allows one request per second — page in bulk rather than polling small pages.

**Parameters**

| Name | In | Required | Type | Description |
| --- | --- | --- | --- | --- |
| `page` | query | no | string, matches ^[1-9]\d*$ |  |
| `cursor` | query | no | string, matches ^[1-9]\d*$ |  |
| `limit` | query | yes | string, one of 500, 1000 |  |
| `app_id` | query | no | string, matches ^[1-9]\d*$ |  |

**Response 200**: 

```json
{
  "type": "object",
  "properties": {
    "listings": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "minimum": 0,
            "exclusiveMinimum": true,
            "maximum": 9007199254740991
          },
          "app_id": {
            "type": "integer",
            "minimum": 0,
            "exclusiveMinimum": true,
            "maximum": 9007199254740991
          },
          "market_hash_name": {
            "type": "string"
          },
          "price": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "amount": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "commodity": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
          },
          "steam_asset_id": {
            "type": "string"
          },
          "icon_url": {
            "type": "string"
          },
          "trade_locked_until": {
            "nullable": true,
            "type": "string",
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
          },
          "cs_weapon": {
            "nullable": true,
            "type": "string"
          },
          "cs_type": {
            "nullable": true,
            "type": "string"
          },
          "cs_wear": {
            "nullable": true,
            "type": "string"
          },
          "cs_rarity": {
            "nullable": true,
            "type": "string"
          },
          "cs_collection": {
            "nullable": true,
            "type": "string"
          },
          "cs_is_stattrak": {
            "nullable": true,
            "type": "boolean"
          },
          "cs_is_souvenir": {
            "nullable": true,
            "type": "boolean"
          },
          "cs_is_highlight": {
            "nullable": true,
            "type": "boolean"
          },
          "cs_inspect_link": {
            "nullable": true,
            "type": "string"
          },
          "cs_paint_wear": {
            "nullable": true,
            "type": "number"
          },
          "cs_paint_seed": {
            "nullable": true,
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "cs_paint_index": {
            "nullable": true,
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "cs_fade_percentage": {
            "nullable": true,
            "type": "number"
          },
          "cs_blue_percentage": {
            "nullable": true,
            "type": "number"
          },
          "cs_stickers": {
            "nullable": true,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "slot": {
                  "type": "integer",
                  "minimum": -9007199254740991,
                  "maximum": 9007199254740991
                },
                "sticker_id": {
                  "type": "integer",
                  "minimum": -9007199254740991,
                  "maximum": 9007199254740991
                },
                "name": {
                  "type": "string"
                },
                "image": {
                  "type": "string"
                },
                "wear": {
                  "nullable": true,
                  "type": "number"
                },
                "scale": {
                  "nullable": true,
                  "type": "number"
                },
                "rotation": {
                  "nullable": true,
                  "type": "number"
                },
                "tint_id": {
                  "nullable": true,
                  "type": "integer",
                  "minimum": -9007199254740991,
                  "maximum": 9007199254740991
                },
                "offset_x": {
                  "nullable": true,
                  "type": "number"
                },
                "offset_y": {
                  "nullable": true,
                  "type": "number"
                },
                "offset_z": {
                  "nullable": true,
                  "type": "number"
                },
                "pattern": {
                  "nullable": true,
                  "type": "integer",
                  "minimum": -9007199254740991,
                  "maximum": 9007199254740991
                }
              },
              "required": [
                "slot",
                "sticker_id",
                "name",
                "image",
                "wear",
                "scale",
                "rotation",
                "tint_id",
                "offset_x",
                "offset_y",
                "offset_z",
                "pattern"
              ],
              "additionalProperties": false
            }
          },
          "cs_keychains": {
            "nullable": true,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "slot": {
                  "type": "integer",
                  "minimum": -9007199254740991,
                  "maximum": 9007199254740991
                },
                "sticker_id": {
                  "type": "integer",
                  "minimum": -9007199254740991,
                  "maximum": 9007199254740991
                },
                "name": {
                  "type": "string"
                },
                "image": {
                  "type": "string"
                },
                "wear": {
                  "nullable": true,
                  "type": "number"
                },
                "scale": {
                  "nullable": true,
                  "type": "number"
                },
                "rotation": {
                  "nullable": true,
                  "type": "number"
                },
                "tint_id": {
                  "nullable": true,
                  "type": "integer",
                  "minimum": -9007199254740991,
                  "maximum": 9007199254740991
                },
                "offset_x": {
                  "nullable": true,
                  "type": "number"
                },
                "offset_y": {
                  "nullable": true,
                  "type": "number"
                },
                "offset_z": {
                  "nullable": true,
                  "type": "number"
                },
                "pattern": {
                  "nullable": true,
                  "type": "integer",
                  "minimum": -9007199254740991,
                  "maximum": 9007199254740991
                }
              },
              "required": [
                "slot",
                "sticker_id",
                "name",
                "image",
                "wear",
                "scale",
                "rotation",
                "tint_id",
                "offset_x",
                "offset_y",
                "offset_z",
                "pattern"
              ],
              "additionalProperties": false
            }
          },
          "rust_category": {
            "nullable": true,
            "type": "string"
          },
          "rust_type": {
            "nullable": true,
            "type": "string"
          },
          "rust_collection": {
            "nullable": true,
            "type": "string"
          },
          "dota_rarity": {
            "nullable": true,
            "type": "string"
          },
          "dota_hero": {
            "nullable": true,
            "type": "string"
          },
          "dota_quality": {
            "nullable": true,
            "type": "string"
          },
          "dota_type": {
            "nullable": true,
            "type": "string"
          },
          "dota_slot": {
            "nullable": true,
            "type": "string"
          },
          "dota_collection": {
            "nullable": true,
            "type": "string"
          },
          "dota_event": {
            "nullable": true,
            "type": "string"
          },
          "tf2_classes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "tf2_quality": {
            "nullable": true,
            "type": "string"
          },
          "tf2_effect": {
            "nullable": true,
            "type": "string"
          },
          "tf2_wear": {
            "nullable": true,
            "type": "string"
          },
          "tf2_spells": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "tf2_warpaint": {
            "nullable": true,
            "type": "string"
          },
          "tf2_sheen": {
            "nullable": true,
            "type": "string"
          },
          "tf2_collection": {
            "nullable": true,
            "type": "string"
          },
          "tf2_grade": {
            "nullable": true,
            "type": "string"
          },
          "tf2_paint_color": {
            "nullable": true,
            "type": "string"
          },
          "tf2_attributes": {
            "nullable": true,
            "type": "object",
            "properties": {
              "craftable": {
                "type": "boolean"
              },
              "uncraftable": {
                "type": "boolean"
              },
              "festivized": {
                "type": "boolean"
              },
              "strange_parts": {
                "type": "boolean"
              },
              "holiday_restricted": {
                "type": "boolean"
              }
            },
            "required": [
              "craftable",
              "uncraftable",
              "festivized",
              "strange_parts",
              "holiday_restricted"
            ],
            "additionalProperties": false
          },
          "tf2_wiki_link": {
            "nullable": true,
            "type": "string"
          },
          "tf2_inspect_link": {
            "nullable": true,
            "type": "string"
          },
          "tf2_type": {
            "nullable": true,
            "type": "string"
          }
        },
        "required": [
          "id",
          "app_id",
          "market_hash_name",
          "price",
          "amount",
          "commodity",
          "created_at",
          "steam_asset_id",
          "icon_url",
          "trade_locked_until",
          "cs_weapon",
          "cs_type",
          "cs_wear",
          "cs_rarity",
          "cs_collection",
          "cs_is_stattrak",
          "cs_is_souvenir",
          "cs_is_highlight",
          "cs_inspect_link",
          "cs_paint_wear",
          "cs_paint_seed",
          "cs_paint_index",
          "cs_fade_percentage",
          "cs_blue_percentage",
          "cs_stickers",
          "cs_keychains",
          "rust_category",
          "rust_type",
          "rust_collection",
          "dota_rarity",
          "dota_hero",
          "dota_quality",
          "dota_type",
          "dota_slot",
          "dota_collection",
          "dota_event",
          "tf2_classes",
          "tf2_quality",
          "tf2_effect",
          "tf2_wear",
          "tf2_spells",
          "tf2_warpaint",
          "tf2_sheen",
          "tf2_collection",
          "tf2_grade",
          "tf2_paint_color",
          "tf2_attributes",
          "tf2_wiki_link",
          "tf2_inspect_link",
          "tf2_type"
        ],
        "additionalProperties": false
      }
    },
    "next_cursor": {
      "nullable": true,
      "type": "integer",
      "minimum": 0,
      "exclusiveMinimum": true,
      "maximum": 9007199254740991
    },
    "metadata": {
      "type": "object",
      "properties": {
        "total_pages": {
          "type": "integer",
          "minimum": -9007199254740991,
          "maximum": 9007199254740991
        },
        "total_items": {
          "type": "integer",
          "minimum": -9007199254740991,
          "maximum": 9007199254740991
        },
        "current_page": {
          "type": "integer",
          "minimum": -9007199254740991,
          "maximum": 9007199254740991
        },
        "current_limit": {
          "type": "integer",
          "minimum": -9007199254740991,
          "maximum": 9007199254740991
        }
      },
      "required": [
        "total_pages",
        "total_items",
        "current_page",
        "current_limit"
      ],
      "additionalProperties": false
    }
  },
  "required": [
    "listings",
    "next_cursor",
    "metadata"
  ],
  "additionalProperties": false
}
```

## Single listing

`GET /public/v1/listings/{id}`

Requires `Authorization: Bearer csd_...`.

One active listing by its id, with the same per-game item fields as `GET /public/v1/listings`. Use it to re-check a listing you already know about — after a `listing.price_changed` event, or right before a purchase — instead of paging the whole book. Returns `404 LISTING_NOT_FOUND` once the listing is sold or delisted, which is the cheapest way to confirm a listing is gone.

**Parameters**

| Name | In | Required | Type | Description |
| --- | --- | --- | --- | --- |
| `id` | path | yes | number | Listing id |

**Response 200**: 

```json
{
  "type": "object",
  "properties": {
    "id": {
      "type": "integer",
      "minimum": 0,
      "exclusiveMinimum": true,
      "maximum": 9007199254740991
    },
    "app_id": {
      "type": "integer",
      "minimum": 0,
      "exclusiveMinimum": true,
      "maximum": 9007199254740991
    },
    "market_hash_name": {
      "type": "string"
    },
    "price": {
      "type": "integer",
      "minimum": -9007199254740991,
      "maximum": 9007199254740991
    },
    "amount": {
      "type": "integer",
      "minimum": -9007199254740991,
      "maximum": 9007199254740991
    },
    "commodity": {
      "type": "boolean"
    },
    "created_at": {
      "type": "string",
      "format": "date-time",
      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
    },
    "steam_asset_id": {
      "type": "string"
    },
    "icon_url": {
      "type": "string"
    },
    "trade_locked_until": {
      "nullable": true,
      "type": "string",
      "format": "date-time",
      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
    },
    "cs_weapon": {
      "nullable": true,
      "type": "string"
    },
    "cs_type": {
      "nullable": true,
      "type": "string"
    },
    "cs_wear": {
      "nullable": true,
      "type": "string"
    },
    "cs_rarity": {
      "nullable": true,
      "type": "string"
    },
    "cs_collection": {
      "nullable": true,
      "type": "string"
    },
    "cs_is_stattrak": {
      "nullable": true,
      "type": "boolean"
    },
    "cs_is_souvenir": {
      "nullable": true,
      "type": "boolean"
    },
    "cs_is_highlight": {
      "nullable": true,
      "type": "boolean"
    },
    "cs_inspect_link": {
      "nullable": true,
      "type": "string"
    },
    "cs_paint_wear": {
      "nullable": true,
      "type": "number"
    },
    "cs_paint_seed": {
      "nullable": true,
      "type": "integer",
      "minimum": -9007199254740991,
      "maximum": 9007199254740991
    },
    "cs_paint_index": {
      "nullable": true,
      "type": "integer",
      "minimum": -9007199254740991,
      "maximum": 9007199254740991
    },
    "cs_fade_percentage": {
      "nullable": true,
      "type": "number"
    },
    "cs_blue_percentage": {
      "nullable": true,
      "type": "number"
    },
    "cs_stickers": {
      "nullable": true,
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "slot": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "sticker_id": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "name": {
            "type": "string"
          },
          "image": {
            "type": "string"
          },
          "wear": {
            "nullable": true,
            "type": "number"
          },
          "scale": {
            "nullable": true,
            "type": "number"
          },
          "rotation": {
            "nullable": true,
            "type": "number"
          },
          "tint_id": {
            "nullable": true,
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "offset_x": {
            "nullable": true,
            "type": "number"
          },
          "offset_y": {
            "nullable": true,
            "type": "number"
          },
          "offset_z": {
            "nullable": true,
            "type": "number"
          },
          "pattern": {
            "nullable": true,
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          }
        },
        "required": [
          "slot",
          "sticker_id",
          "name",
          "image",
          "wear",
          "scale",
          "rotation",
          "tint_id",
          "offset_x",
          "offset_y",
          "offset_z",
          "pattern"
        ],
        "additionalProperties": false
      }
    },
    "cs_keychains": {
      "nullable": true,
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "slot": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "sticker_id": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "name": {
            "type": "string"
          },
          "image": {
            "type": "string"
          },
          "wear": {
            "nullable": true,
            "type": "number"
          },
          "scale": {
            "nullable": true,
            "type": "number"
          },
          "rotation": {
            "nullable": true,
            "type": "number"
          },
          "tint_id": {
            "nullable": true,
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "offset_x": {
            "nullable": true,
            "type": "number"
          },
          "offset_y": {
            "nullable": true,
            "type": "number"
          },
          "offset_z": {
            "nullable": true,
            "type": "number"
          },
          "pattern": {
            "nullable": true,
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          }
        },
        "required": [
          "slot",
          "sticker_id",
          "name",
          "image",
          "wear",
          "scale",
          "rotation",
          "tint_id",
          "offset_x",
          "offset_y",
          "offset_z",
          "pattern"
        ],
        "additionalProperties": false
      }
    },
    "rust_category": {
      "nullable": true,
      "type": "string"
    },
    "rust_type": {
      "nullable": true,
      "type": "string"
    },
    "rust_collection": {
      "nullable": true,
      "type": "string"
    },
    "dota_rarity": {
      "nullable": true,
      "type": "string"
    },
    "dota_hero": {
      "nullable": true,
      "type": "string"
    },
    "dota_quality": {
      "nullable": true,
      "type": "string"
    },
    "dota_type": {
      "nullable": true,
      "type": "string"
    },
    "dota_slot": {
      "nullable": true,
      "type": "string"
    },
    "dota_collection": {
      "nullable": true,
      "type": "string"
    },
    "dota_event": {
      "nullable": true,
      "type": "string"
    },
    "tf2_classes": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "tf2_quality": {
      "nullable": true,
      "type": "string"
    },
    "tf2_effect": {
      "nullable": true,
      "type": "string"
    },
    "tf2_wear": {
      "nullable": true,
      "type": "string"
    },
    "tf2_spells": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "tf2_warpaint": {
      "nullable": true,
      "type": "string"
    },
    "tf2_sheen": {
      "nullable": true,
      "type": "string"
    },
    "tf2_collection": {
      "nullable": true,
      "type": "string"
    },
    "tf2_grade": {
      "nullable": true,
      "type": "string"
    },
    "tf2_paint_color": {
      "nullable": true,
      "type": "string"
    },
    "tf2_attributes": {
      "nullable": true,
      "type": "object",
      "properties": {
        "craftable": {
          "type": "boolean"
        },
        "uncraftable": {
          "type": "boolean"
        },
        "festivized": {
          "type": "boolean"
        },
        "strange_parts": {
          "type": "boolean"
        },
        "holiday_restricted": {
          "type": "boolean"
        }
      },
      "required": [
        "craftable",
        "uncraftable",
        "festivized",
        "strange_parts",
        "holiday_restricted"
      ],
      "additionalProperties": false
    },
    "tf2_wiki_link": {
      "nullable": true,
      "type": "string"
    },
    "tf2_inspect_link": {
      "nullable": true,
      "type": "string"
    },
    "tf2_type": {
      "nullable": true,
      "type": "string"
    }
  },
  "required": [
    "id",
    "app_id",
    "market_hash_name",
    "price",
    "amount",
    "commodity",
    "created_at",
    "steam_asset_id",
    "icon_url",
    "trade_locked_until",
    "cs_weapon",
    "cs_type",
    "cs_wear",
    "cs_rarity",
    "cs_collection",
    "cs_is_stattrak",
    "cs_is_souvenir",
    "cs_is_highlight",
    "cs_inspect_link",
    "cs_paint_wear",
    "cs_paint_seed",
    "cs_paint_index",
    "cs_fade_percentage",
    "cs_blue_percentage",
    "cs_stickers",
    "cs_keychains",
    "rust_category",
    "rust_type",
    "rust_collection",
    "dota_rarity",
    "dota_hero",
    "dota_quality",
    "dota_type",
    "dota_slot",
    "dota_collection",
    "dota_event",
    "tf2_classes",
    "tf2_quality",
    "tf2_effect",
    "tf2_wear",
    "tf2_spells",
    "tf2_warpaint",
    "tf2_sheen",
    "tf2_collection",
    "tf2_grade",
    "tf2_paint_color",
    "tf2_attributes",
    "tf2_wiki_link",
    "tf2_inspect_link",
    "tf2_type"
  ],
  "additionalProperties": false
}
```

## Book snapshot

`GET /public/v1/book`

Requires `Authorization: Bearer csd_...`.

Every active listing in one response, plus `seq` (an `int64`), the sequence number of the last WebSocket event published before the snapshot was taken. Use it to seed a local book: buffer WebSocket events, load the snapshot, drop events with `seq <=` the snapshot's, apply the rest. Optional `app_id` filter, worth passing since it cuts a large payload down to the game you trade. Rows are deliberately lean: price/amount state only, no per-game item fields; item detail comes from `GET /public/v1/listings` or the `listing.created` events. Snapshots are cached for 10 seconds and carry an `ETag`, so a repeat call within that window is free; the cached `seq` is the one the snapshot is valid at, which is all the sync protocol needs. Heavily rate-limited: it exists for (re)synchronization, not polling. If you want item prices rather than individual live listings, use `GET /public/v1/prices/all` instead, which is far cheaper for both of us.

**Parameters**

| Name | In | Required | Type | Description |
| --- | --- | --- | --- | --- |
| `app_id` | query | no | string, matches ^[1-9]\d*$ |  |

**Response 200**: 

```json
{
  "type": "object",
  "properties": {
    "seq": {
      "type": "integer",
      "minimum": 0,
      "maximum": 9007199254740991
    },
    "listings": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "minimum": 0,
            "exclusiveMinimum": true,
            "maximum": 9007199254740991
          },
          "app_id": {
            "type": "integer",
            "minimum": 0,
            "exclusiveMinimum": true,
            "maximum": 9007199254740991
          },
          "market_hash_name": {
            "type": "string"
          },
          "price": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "amount": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "commodity": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
          }
        },
        "required": [
          "id",
          "app_id",
          "market_hash_name",
          "price",
          "amount",
          "commodity",
          "created_at"
        ],
        "additionalProperties": false
      }
    }
  },
  "required": [
    "seq",
    "listings"
  ],
  "additionalProperties": false
}
```

## Prices

`GET /public/v1/prices`

Requires `Authorization: Bearer csd_...`.

Paginated price list per market hash name, ordered alphabetically. Filter by `app_id`. Prices are integers in cents; `market_price` is the Steam-derived market value and `recommended_price` the suggested listing price; `lowest_listing_price` is the cheapest live CS Deals listing for that item right now, or `null` when nobody is selling one. `lowest_listing_price` is the v1 `IPricing/GetLowestPrices` number. There are hundreds of thousands of priced items, so paging through the whole set is slow: use `GET /public/v1/prices/all` for a full snapshot and keep this endpoint for spot checks.

**Parameters**

| Name | In | Required | Type | Description |
| --- | --- | --- | --- | --- |
| `page` | query | yes | string, matches ^[1-9]\d*$ |  |
| `limit` | query | yes | string, one of 5, 10, 25, 30, 50, 100 |  |
| `app_id` | query | no | string, matches ^[1-9]\d*$ |  |

**Response 200**: 

```json
{
  "type": "object",
  "properties": {
    "prices": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "app_id": {
            "type": "integer",
            "minimum": 0,
            "exclusiveMinimum": true,
            "maximum": 9007199254740991
          },
          "market_hash_name": {
            "type": "string"
          },
          "market_price": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "recommended_price": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "lowest_listing_price": {
            "nullable": true,
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "updated_at": {
            "nullable": true,
            "type": "string",
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
          }
        },
        "required": [
          "app_id",
          "market_hash_name",
          "market_price",
          "recommended_price",
          "lowest_listing_price",
          "updated_at"
        ],
        "additionalProperties": false
      }
    },
    "metadata": {
      "type": "object",
      "properties": {
        "total_pages": {
          "type": "integer",
          "minimum": -9007199254740991,
          "maximum": 9007199254740991
        },
        "total_items": {
          "type": "integer",
          "minimum": -9007199254740991,
          "maximum": 9007199254740991
        },
        "current_page": {
          "type": "integer",
          "minimum": -9007199254740991,
          "maximum": 9007199254740991
        },
        "current_limit": {
          "type": "integer",
          "minimum": -9007199254740991,
          "maximum": 9007199254740991
        }
      },
      "required": [
        "total_pages",
        "total_items",
        "current_page",
        "current_limit"
      ],
      "additionalProperties": false
    }
  },
  "required": [
    "prices",
    "metadata"
  ],
  "additionalProperties": false
}
```

## Every price at once

`GET /public/v1/prices/all`

Requires `Authorization: Bearer csd_...`.

Every item price in one response, no pagination. The whole set is built once and cached for 60 seconds, so this is the cheap way to keep a price table current: prefer it to paging through `GET /public/v1/prices`, and prefer it to `GET /public/v1/book` when you want item prices rather than individual live listings. Each row carries `lowest_listing_price`, the cheapest live CS Deals listing for that item, alongside the Steam-derived `market_price`. Optional `app_id` filter, which is worth passing since it cuts the payload to the game you trade. Responses carry an `ETag`; send it back as `If-None-Match` and you get a `304` when nothing has changed. `generated_at` is when the cached copy was built.

**Parameters**

| Name | In | Required | Type | Description |
| --- | --- | --- | --- | --- |
| `app_id` | query | no | string, matches ^[1-9]\d*$ |  |

**Response 200**: 

```json
{
  "type": "object",
  "properties": {
    "prices": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "app_id": {
            "type": "integer",
            "minimum": 0,
            "exclusiveMinimum": true,
            "maximum": 9007199254740991
          },
          "market_hash_name": {
            "type": "string"
          },
          "market_price": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "recommended_price": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "lowest_listing_price": {
            "nullable": true,
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "updated_at": {
            "nullable": true,
            "type": "string",
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
          }
        },
        "required": [
          "app_id",
          "market_hash_name",
          "market_price",
          "recommended_price",
          "lowest_listing_price",
          "updated_at"
        ],
        "additionalProperties": false
      }
    },
    "generated_at": {
      "type": "string",
      "format": "date-time",
      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
    }
  },
  "required": [
    "prices",
    "generated_at"
  ],
  "additionalProperties": false
}
```

## Sales history

`GET /public/v1/sales`

Requires `Authorization: Bearer csd_...`.

Recent marketplace sales, newest first. Filter by `app_id` and/or exact `market_hash_name`. Each row is one sold order line: the price paid in cents, the amount, and when it sold.

**Parameters**

| Name | In | Required | Type | Description |
| --- | --- | --- | --- | --- |
| `page` | query | yes | string, matches ^[1-9]\d*$ |  |
| `limit` | query | yes | string, one of 5, 10, 25, 30, 50, 100 |  |
| `app_id` | query | no | string, matches ^[1-9]\d*$ |  |
| `market_hash_name` | query | no | string |  |

**Response 200**: 

```json
{
  "type": "object",
  "properties": {
    "sales": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "app_id": {
            "type": "integer",
            "minimum": 0,
            "exclusiveMinimum": true,
            "maximum": 9007199254740991
          },
          "market_hash_name": {
            "type": "string"
          },
          "price": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "amount": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "sold_at": {
            "type": "string",
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
          }
        },
        "required": [
          "app_id",
          "market_hash_name",
          "price",
          "amount",
          "sold_at"
        ],
        "additionalProperties": false
      }
    },
    "metadata": {
      "type": "object",
      "properties": {
        "total_pages": {
          "type": "integer",
          "minimum": -9007199254740991,
          "maximum": 9007199254740991
        },
        "total_items": {
          "type": "integer",
          "minimum": -9007199254740991,
          "maximum": 9007199254740991
        },
        "current_page": {
          "type": "integer",
          "minimum": -9007199254740991,
          "maximum": 9007199254740991
        },
        "current_limit": {
          "type": "integer",
          "minimum": -9007199254740991,
          "maximum": 9007199254740991
        }
      },
      "required": [
        "total_pages",
        "total_items",
        "current_page",
        "current_limit"
      ],
      "additionalProperties": false
    }
  },
  "required": [
    "sales",
    "metadata"
  ],
  "additionalProperties": false
}
```

## Buy listings

`POST /public/v1/purchase`

Requires `Authorization: Bearer csd_...`.

Buys the listings named in the request in a single atomic call. Nothing is
bought unless every line succeeds.

`max_price` is a ceiling in cents, not an exact match: a listing that got
cheaper between your quote and this call still fills, one that got more
expensive fails with `LISTING_PRICE_CHANGED`. Set it high to opt out.

This does not read or modify your cart, so a bot and a browser session on the
same account never interfere with each other.

Private listings are not in any feed and are bought by passing the
`private_token` from the share link on that line. Without it the line comes
back as `LISTING_NOT_FOUND`, the same as an id that does not exist.

**Response 200**: 

```json
{
  "type": "object",
  "properties": {
    "order_id": {
      "type": "integer",
      "minimum": 0,
      "exclusiveMinimum": true,
      "maximum": 9007199254740991
    },
    "created_at": {
      "type": "string",
      "format": "date-time",
      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
    },
    "items": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "order_item_id": {
            "type": "integer",
            "minimum": 0,
            "exclusiveMinimum": true,
            "maximum": 9007199254740991
          },
          "app_id": {
            "type": "integer",
            "minimum": 0,
            "exclusiveMinimum": true,
            "maximum": 9007199254740991
          },
          "market_hash_name": {
            "type": "string"
          },
          "steam_asset_id": {
            "type": "string"
          },
          "price": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "amount": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          }
        },
        "required": [
          "order_item_id",
          "app_id",
          "market_hash_name",
          "steam_asset_id",
          "price",
          "amount"
        ],
        "additionalProperties": false
      }
    }
  },
  "required": [
    "order_id",
    "created_at",
    "items"
  ],
  "additionalProperties": false
}
```

## Check your API key

`GET /auth/api-key`

Requires `Authorization: Bearer csd_...`.

**Response 200**: 

## Your account

`GET /public/v1/user`

Requires `Authorization: Bearer csd_...`.

Account snapshot: CS Deals user id, linked `steam_id`, display name, and balance in cents.

**Response 200**: 

```json
{
  "type": "object",
  "properties": {
    "id": {
      "type": "integer",
      "minimum": 0,
      "exclusiveMinimum": true,
      "maximum": 9007199254740991
    },
    "steam_id": {
      "nullable": true,
      "type": "string"
    },
    "name": {
      "type": "string"
    },
    "balance": {
      "type": "integer",
      "minimum": -9007199254740991,
      "maximum": 9007199254740991
    }
  },
  "required": [
    "id",
    "steam_id",
    "name",
    "balance"
  ],
  "additionalProperties": false
}
```

## Order history

`GET /public/v1/orders`

Requires `Authorization: Bearer csd_...`.

Your order items, newest first. `side` is `bought` when you were the buyer and `sold` when you were the seller. Prices are integers in cents.

**Parameters**

| Name | In | Required | Type | Description |
| --- | --- | --- | --- | --- |
| `page` | query | yes | string, matches ^[1-9]\d*$ |  |
| `limit` | query | yes | string, one of 5, 10, 25, 30, 50, 100 |  |

**Response 200**: 

```json
{
  "type": "object",
  "properties": {
    "orders": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "order_item_id": {
            "type": "integer",
            "minimum": 0,
            "exclusiveMinimum": true,
            "maximum": 9007199254740991
          },
          "order_id": {
            "type": "integer",
            "minimum": 0,
            "exclusiveMinimum": true,
            "maximum": 9007199254740991
          },
          "side": {
            "type": "string",
            "enum": [
              "bought",
              "sold"
            ]
          },
          "app_id": {
            "type": "integer",
            "minimum": 0,
            "exclusiveMinimum": true,
            "maximum": 9007199254740991
          },
          "market_hash_name": {
            "type": "string"
          },
          "price": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "amount": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
          }
        },
        "required": [
          "order_item_id",
          "order_id",
          "side",
          "app_id",
          "market_hash_name",
          "price",
          "amount",
          "created_at"
        ],
        "additionalProperties": false
      }
    },
    "metadata": {
      "type": "object",
      "properties": {
        "total_pages": {
          "type": "integer",
          "minimum": -9007199254740991,
          "maximum": 9007199254740991
        },
        "total_items": {
          "type": "integer",
          "minimum": -9007199254740991,
          "maximum": 9007199254740991
        },
        "current_page": {
          "type": "integer",
          "minimum": -9007199254740991,
          "maximum": 9007199254740991
        },
        "current_limit": {
          "type": "integer",
          "minimum": -9007199254740991,
          "maximum": 9007199254740991
        }
      },
      "required": [
        "total_pages",
        "total_items",
        "current_page",
        "current_limit"
      ],
      "additionalProperties": false
    }
  },
  "required": [
    "orders",
    "metadata"
  ],
  "additionalProperties": false
}
```

## Export your order history

`GET /public/v1/orders/export`

Requires `Authorization: Bearer csd_...`.

Your whole order history in one download, no pagination: every item you bought and sold, oldest first. `format=csv` (the default) returns a spreadsheet with a header row; `format=json` returns `{ "orders": [...] }` with the same columns. Narrow it with `side` (`bought` or `sold`), `app_id`, and `from`/`to` as ISO dates. Money is in integer cents: `unit_price` is the price of one copy, `total_value` the whole row, `fee` the selling commission (only ever set on your `sold` rows) and `net` what the row was worth to you. `settled_at` is when a sale settled and is empty until it does. The response is streamed, so a long history starts arriving immediately.

**Parameters**

| Name | In | Required | Type | Description |
| --- | --- | --- | --- | --- |
| `format` | query | no | string, one of csv, json, default csv |  |
| `side` | query | no | string, one of bought, sold |  |
| `app_id` | query | no | string, matches ^[1-9]\d*$ |  |
| `from` | query | no | string |  |
| `to` | query | no | string |  |

**Response 200**: CSV when `format=csv`, otherwise the JSON body described here.

```json
{
  "type": "object",
  "properties": {
    "orders": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "order_item_id": {
            "type": "integer",
            "minimum": 0,
            "exclusiveMinimum": true,
            "maximum": 9007199254740991
          },
          "order_id": {
            "type": "integer",
            "minimum": 0,
            "exclusiveMinimum": true,
            "maximum": 9007199254740991
          },
          "side": {
            "type": "string",
            "enum": [
              "bought",
              "sold"
            ]
          },
          "app_id": {
            "type": "integer",
            "minimum": 0,
            "exclusiveMinimum": true,
            "maximum": 9007199254740991
          },
          "market_hash_name": {
            "type": "string"
          },
          "amount": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "unit_price": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "total_value": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "fee": {
            "nullable": true,
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "net": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
          },
          "settled_at": {
            "nullable": true,
            "type": "string",
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
          }
        },
        "required": [
          "order_item_id",
          "order_id",
          "side",
          "app_id",
          "market_hash_name",
          "amount",
          "unit_price",
          "total_value",
          "fee",
          "net",
          "created_at",
          "settled_at"
        ],
        "additionalProperties": false
      }
    }
  },
  "required": [
    "orders"
  ],
  "additionalProperties": false
}
```

## Balance history

`GET /public/v1/transactions`

Requires `Authorization: Bearer csd_...`.

Every movement of your balance, newest first, optionally filtered by `action` (`DEPOSIT`, `WITHDRAWAL`, `WITHDRAWAL_REFUND`, `INSTANT_SELL_PAYOUT`, `PURCHASE`, `SALE`, `BALANCE_ADJUSTMENT`). `amount` is signed in cents, negative when money left the account; `balance` is the resulting balance after that movement, so a page of rows reconciles without adding anything up yourself. `message` is a human-readable note such as the order or withdrawal it belongs to.

**Parameters**

| Name | In | Required | Type | Description |
| --- | --- | --- | --- | --- |
| `page` | query | yes | string, matches ^[1-9]\d*$ |  |
| `limit` | query | yes | string, one of 5, 10, 25, 30, 50, 100 |  |
| `action` | query | no | string, one of DEPOSIT, WITHDRAWAL, WITHDRAWAL_REFUND, INSTANT_SELL_PAYOUT, PURCHASE, SALE, BALANCE_ADJUSTMENT |  |

**Response 200**: 

```json
{
  "type": "object",
  "properties": {
    "transactions": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "minimum": 0,
            "exclusiveMinimum": true,
            "maximum": 9007199254740991
          },
          "action": {
            "type": "string",
            "enum": [
              "DEPOSIT",
              "WITHDRAWAL",
              "WITHDRAWAL_REFUND",
              "INSTANT_SELL_PAYOUT",
              "PURCHASE",
              "SALE",
              "BALANCE_ADJUSTMENT"
            ]
          },
          "amount": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "balance": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "message": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
          }
        },
        "required": [
          "id",
          "action",
          "amount",
          "balance",
          "message",
          "created_at"
        ],
        "additionalProperties": false
      }
    },
    "metadata": {
      "type": "object",
      "properties": {
        "total_pages": {
          "type": "integer",
          "minimum": -9007199254740991,
          "maximum": 9007199254740991
        },
        "total_items": {
          "type": "integer",
          "minimum": -9007199254740991,
          "maximum": 9007199254740991
        },
        "current_page": {
          "type": "integer",
          "minimum": -9007199254740991,
          "maximum": 9007199254740991
        },
        "current_limit": {
          "type": "integer",
          "minimum": -9007199254740991,
          "maximum": 9007199254740991
        }
      },
      "required": [
        "total_pages",
        "total_items",
        "current_page",
        "current_limit"
      ],
      "additionalProperties": false
    }
  },
  "required": [
    "transactions",
    "metadata"
  ],
  "additionalProperties": false
}
```

## Your backpack

`GET /public/v1/backpack`

Requires `Authorization: Bearer csd_...`.

Your on-site items, paginated. Each row's `id` is the backpack item id, the value you pass to withdraw. `trade_locked_until` is set while Steam's trade hold applies. Optional `app_id` and `search` filters.

**Parameters**

| Name | In | Required | Type | Description |
| --- | --- | --- | --- | --- |
| `page` | query | yes | string, matches ^[1-9]\d*$ |  |
| `limit` | query | yes | string, one of 5, 10, 25, 30, 50, 100 |  |
| `app_id` | query | no | string, matches ^[1-9]\d*$ |  |
| `search` | query | no | string |  |

**Response 200**: 

```json
{
  "type": "object",
  "properties": {
    "items": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "minimum": 0,
            "exclusiveMinimum": true,
            "maximum": 9007199254740991
          },
          "app_id": {
            "type": "integer",
            "minimum": 0,
            "exclusiveMinimum": true,
            "maximum": 9007199254740991
          },
          "market_hash_name": {
            "type": "string"
          },
          "amount": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "commodity": {
            "type": "boolean"
          },
          "market_price": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "trade_locked_until": {
            "nullable": true,
            "type": "string",
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
          }
        },
        "required": [
          "id",
          "app_id",
          "market_hash_name",
          "amount",
          "commodity",
          "market_price",
          "trade_locked_until"
        ],
        "additionalProperties": false
      }
    },
    "metadata": {
      "type": "object",
      "properties": {
        "total_pages": {
          "type": "integer",
          "minimum": -9007199254740991,
          "maximum": 9007199254740991
        },
        "total_items": {
          "type": "integer",
          "minimum": -9007199254740991,
          "maximum": 9007199254740991
        },
        "current_page": {
          "type": "integer",
          "minimum": -9007199254740991,
          "maximum": 9007199254740991
        },
        "current_limit": {
          "type": "integer",
          "minimum": -9007199254740991,
          "maximum": 9007199254740991
        }
      },
      "required": [
        "total_pages",
        "total_items",
        "current_page",
        "current_limit"
      ],
      "additionalProperties": false
    }
  },
  "required": [
    "items",
    "metadata"
  ],
  "additionalProperties": false
}
```

## Withdraw to Steam

`POST /public/v1/withdraw`

Requires `Authorization: Bearer csd_...`.

Sends backpack items to your Steam account as trade offers. Items held by different bots become separate offers; the response's `withdraw_ids` has one id per offer, matching the `withdraw_id` on `/trades` rows. Requires a Steam trade URL on the account. At most 50 items per request.

**Response 200**: 

```json
{
  "type": "object",
  "properties": {
    "withdraw_ids": {
      "type": "array",
      "items": {
        "type": "integer",
        "minimum": 0,
        "exclusiveMinimum": true,
        "maximum": 9007199254740991
      }
    }
  },
  "required": [
    "withdraw_ids"
  ],
  "additionalProperties": false
}
```

## Your trades

`GET /public/v1/trades`

Requires `Authorization: Bearer csd_...`.

Your Steam trades, newest first, optionally filtered by `status`. `steam_offer_id` is the Steam trade offer id once the offer is sent. Item `value` is the market value in cents at trade time. `limit` accepts only `500` or `1000`, and the route allows one request per second — page in bulk rather than polling small pages.

**Parameters**

| Name | In | Required | Type | Description |
| --- | --- | --- | --- | --- |
| `page` | query | yes | string, matches ^[1-9]\d*$ |  |
| `limit` | query | yes | string, one of 500, 1000 |  |
| `status` | query | no | string, one of Invalid, Active, Accepted, Countered, Expired, Canceled, Declined, InvalidItems, CreatedNeedsConfirmation, CanceledBySecondFactor, InEscrow, Reversed |  |

**Response 200**: 

```json
{
  "type": "object",
  "properties": {
    "trades": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "minimum": 0,
            "exclusiveMinimum": true,
            "maximum": 9007199254740991
          },
          "type": {
            "type": "string",
            "enum": [
              "SELL",
              "INSTANT_SELL",
              "WITHDRAW",
              "LEGACY_DEPOSIT",
              "LEGACY_WITHDRAW"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "Invalid",
              "Active",
              "Accepted",
              "Countered",
              "Expired",
              "Canceled",
              "Declined",
              "InvalidItems",
              "CreatedNeedsConfirmation",
              "CanceledBySecondFactor",
              "InEscrow",
              "Reversed"
            ]
          },
          "deposit_id": {
            "nullable": true,
            "type": "integer",
            "minimum": 0,
            "exclusiveMinimum": true,
            "maximum": 9007199254740991
          },
          "withdraw_id": {
            "nullable": true,
            "type": "integer",
            "minimum": 0,
            "exclusiveMinimum": true,
            "maximum": 9007199254740991
          },
          "steam_offer_id": {
            "nullable": true,
            "type": "string"
          },
          "value": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "error": {
            "nullable": true,
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
          },
          "updated_at": {
            "nullable": true,
            "type": "string",
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
          },
          "items": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "app_id": {
                  "type": "integer",
                  "minimum": 0,
                  "exclusiveMinimum": true,
                  "maximum": 9007199254740991
                },
                "market_hash_name": {
                  "type": "string"
                },
                "steam_asset_id": {
                  "type": "string"
                },
                "amount": {
                  "type": "integer",
                  "minimum": -9007199254740991,
                  "maximum": 9007199254740991
                },
                "value": {
                  "type": "integer",
                  "minimum": -9007199254740991,
                  "maximum": 9007199254740991
                }
              },
              "required": [
                "app_id",
                "market_hash_name",
                "steam_asset_id",
                "amount",
                "value"
              ],
              "additionalProperties": false
            }
          }
        },
        "required": [
          "id",
          "type",
          "status",
          "deposit_id",
          "withdraw_id",
          "steam_offer_id",
          "value",
          "error",
          "created_at",
          "updated_at",
          "items"
        ],
        "additionalProperties": false
      }
    },
    "metadata": {
      "type": "object",
      "properties": {
        "total_pages": {
          "type": "integer",
          "minimum": -9007199254740991,
          "maximum": 9007199254740991
        },
        "total_items": {
          "type": "integer",
          "minimum": -9007199254740991,
          "maximum": 9007199254740991
        },
        "current_page": {
          "type": "integer",
          "minimum": -9007199254740991,
          "maximum": 9007199254740991
        },
        "current_limit": {
          "type": "integer",
          "minimum": -9007199254740991,
          "maximum": 9007199254740991
        }
      },
      "required": [
        "total_pages",
        "total_items",
        "current_page",
        "current_limit"
      ],
      "additionalProperties": false
    }
  },
  "required": [
    "trades",
    "metadata"
  ],
  "additionalProperties": false
}
```

## Your Steam inventory

`GET /public/v1/steam-inventory`

Requires `Authorization: Bearer csd_...`.

What is currently in your Steam inventory for one game, ready to be sold here. Each row carries a `token`: an opaque, signed handle for that item which you hand to `POST /public/v1/sell`. Tokens are tied to your account and expire after 30 minutes, so fetch, decide and sell in one pass rather than storing them. `amount` is how many copies that stack holds, and `tradable` is false while Steam is holding the item. This reads live from Steam, so it is heavily rate-limited.

**Parameters**

| Name | In | Required | Type | Description |
| --- | --- | --- | --- | --- |
| `app_id` | query | yes | string, matches ^[1-9]\d*$ |  |

**Response 200**: 

```json
{
  "type": "object",
  "properties": {
    "items": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "token": {
            "type": "string"
          },
          "app_id": {
            "type": "integer",
            "minimum": 0,
            "exclusiveMinimum": true,
            "maximum": 9007199254740991
          },
          "steam_asset_id": {
            "type": "string"
          },
          "market_hash_name": {
            "type": "string"
          },
          "amount": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "commodity": {
            "type": "boolean"
          },
          "tradable": {
            "type": "boolean"
          },
          "market_price": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "recommended_price": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "icon_url": {
            "type": "string"
          }
        },
        "required": [
          "token",
          "app_id",
          "steam_asset_id",
          "market_hash_name",
          "amount",
          "commodity",
          "tradable",
          "market_price",
          "recommended_price",
          "icon_url"
        ],
        "additionalProperties": false
      }
    }
  },
  "required": [
    "items"
  ],
  "additionalProperties": false
}
```

## Sell from Steam

`POST /public/v1/sell`

Requires `Authorization: Bearer csd_...`.

Lists items straight from your Steam inventory: we send you a trade offer for them, and they go on the market at your price once you accept. `items` holds `token`s from `GET /public/v1/steam-inventory` with how many of each to sell, and `price` is per copy in cents. Items of the same type can share a listing; different types need their own group. This is the v1 `ISales/ListItems` flow with the Steam half intact. Each response `deposit_id` matches the `deposit_id` on `GET /public/v1/trades` rows, which is where you watch for the offer and its status. Items already on site are listed with `POST /public/v1/list` instead, no trade offer needed.

**Response 200**: 

```json
{
  "type": "object",
  "properties": {
    "deposit_ids": {
      "type": "array",
      "items": {
        "type": "integer",
        "minimum": 0,
        "exclusiveMinimum": true,
        "maximum": 9007199254740991
      }
    }
  },
  "required": [
    "deposit_ids"
  ],
  "additionalProperties": false
}
```

## List items for sale

`POST /public/v1/list`

Requires `Authorization: Bearer csd_...`.

Puts backpack items on the market. Each group in `listings` becomes one listing at the given `price` in cents; `items` holds backpack item ids (the `id` from `GET /public/v1/backpack`) with the quantity of each to sell. Identical commodity items grouped together sell as one stack. A commodity backpack row is a quantity rather than a single copy, so the same `id` may appear in several groups — that is how you get separate listings for copies of one item at the same price; the groups draw from one pool, and asking for more than the row's `amount` in total fails with `INSUFFICIENT_ITEMS`. At most 50 groups per request, 50 items per group. Auto-decaying prices are website-only. To sell something still sitting in Steam, use `POST /public/v1/sell` instead. Accounts have a ceiling on how much they can have listed at once; past it, listing fails with `LISTING_LIMIT_REACHED`. Contact support if you are hitting it.

**Response 200**: 

```json
{
  "type": "object",
  "properties": {
    "listings": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "minimum": 0,
            "exclusiveMinimum": true,
            "maximum": 9007199254740991
          },
          "app_id": {
            "type": "integer",
            "minimum": 0,
            "exclusiveMinimum": true,
            "maximum": 9007199254740991
          },
          "market_hash_name": {
            "type": "string"
          },
          "price": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "amount": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "commodity": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
          }
        },
        "required": [
          "id",
          "app_id",
          "market_hash_name",
          "price",
          "amount",
          "commodity",
          "created_at"
        ],
        "additionalProperties": false
      }
    }
  },
  "required": [
    "listings"
  ],
  "additionalProperties": false
}
```

## Edit a listing

`PATCH /public/v1/list`

Requires `Authorization: Bearer csd_...`.

Changes the `price` (cents) or `amount` of your active listings. Send a single listing body, or a `listings` array of up to 50 to edit in bulk — the per-listing semantics are identical either way. In bulk each listing is applied in its own transaction, so a failure affects only that row: every entry comes back in `results` with `ok` and, when it failed, the `error` code the single form would have returned, in the order you sent them. Send either field or both. Raising `amount` takes more of the same item from your backpack. Lowering it returns the surplus to your backpack — *unless* you send `price` at the same time, which is a partial reprice: the listing keeps the `amount` you asked for at the new price and the surplus stays on the market in a new listing at the old price, so repricing part of a stack never takes the rest off sale. The response is always the listing you edited; find the new one through `GET /public/v1/my-listings`.

**Response 200**: 

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "anyOf": [
    {
      "type": "object",
      "properties": {
        "id": {
          "type": "integer",
          "exclusiveMinimum": 0,
          "maximum": 9007199254740991
        },
        "app_id": {
          "type": "integer",
          "exclusiveMinimum": 0,
          "maximum": 9007199254740991
        },
        "market_hash_name": {
          "type": "string"
        },
        "price": {
          "type": "integer",
          "minimum": -9007199254740991,
          "maximum": 9007199254740991
        },
        "amount": {
          "type": "integer",
          "minimum": -9007199254740991,
          "maximum": 9007199254740991
        },
        "commodity": {
          "type": "boolean"
        },
        "created_at": {
          "type": "string",
          "format": "date-time",
          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
        }
      },
      "required": [
        "id",
        "app_id",
        "market_hash_name",
        "price",
        "amount",
        "commodity",
        "created_at"
      ],
      "additionalProperties": false
    },
    {
      "type": "object",
      "properties": {
        "results": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "listing_id": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              },
              "ok": {
                "type": "boolean"
              },
              "error": {
                "anyOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "listing": {
                "anyOf": [
                  {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "integer",
                        "exclusiveMinimum": 0,
                        "maximum": 9007199254740991
                      },
                      "app_id": {
                        "type": "integer",
                        "exclusiveMinimum": 0,
                        "maximum": 9007199254740991
                      },
                      "market_hash_name": {
                        "type": "string"
                      },
                      "price": {
                        "type": "integer",
                        "minimum": -9007199254740991,
                        "maximum": 9007199254740991
                      },
                      "amount": {
                        "type": "integer",
                        "minimum": -9007199254740991,
                        "maximum": 9007199254740991
                      },
                      "commodity": {
                        "type": "boolean"
                      },
                      "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
                      }
                    },
                    "required": [
                      "id",
                      "app_id",
                      "market_hash_name",
                      "price",
                      "amount",
                      "commodity",
                      "created_at"
                    ],
                    "additionalProperties": false
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            },
            "required": [
              "listing_id",
              "ok",
              "error",
              "listing"
            ],
            "additionalProperties": false
          }
        }
      },
      "required": [
        "results"
      ],
      "additionalProperties": false
    }
  ]
}
```

## Delist

`POST /public/v1/delist`

Requires `Authorization: Bearer csd_...`.

Takes your listings off the market and returns their items to your backpack. Send `listing_id` for one, or a `listing_ids` array of up to 50 — in bulk each is removed in its own transaction and every id comes back in `results` with `ok` and any `error`, in the order you sent them. The v1 equivalent was `ISales/ReturnItems`.

**Response 200**: 

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "anyOf": [
    {
      "type": "object",
      "properties": {
        "listing_id": {
          "type": "integer",
          "exclusiveMinimum": 0,
          "maximum": 9007199254740991
        }
      },
      "required": [
        "listing_id"
      ],
      "additionalProperties": false
    },
    {
      "type": "object",
      "properties": {
        "results": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "listing_id": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              },
              "ok": {
                "type": "boolean"
              },
              "error": {
                "anyOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            },
            "required": [
              "listing_id",
              "ok",
              "error"
            ],
            "additionalProperties": false
          }
        }
      },
      "required": [
        "results"
      ],
      "additionalProperties": false
    }
  ]
}
```

## Your listings

`GET /public/v1/my-listings`

Requires `Authorization: Bearer csd_...`.

Your own listings, newest first, filterable by `app_id` and `status` (`ACTIVE`, `DISABLED`, `FILLED`). `available_amount` is how many more copies of a commodity listing you still hold in your backpack. Rows are lean: item detail for a listing is in `GET /public/v1/listings`.

**Parameters**

| Name | In | Required | Type | Description |
| --- | --- | --- | --- | --- |
| `page` | query | yes | string, matches ^[1-9]\d*$ |  |
| `limit` | query | yes | string, one of 5, 10, 25, 30, 50, 100 |  |
| `app_id` | query | no | string, matches ^[1-9]\d*$ |  |
| `status` | query | no | string, one of ACTIVE, DISABLED, FILLED, PRIVATE |  |

**Response 200**: 

```json
{
  "type": "object",
  "properties": {
    "listings": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "minimum": 0,
            "exclusiveMinimum": true,
            "maximum": 9007199254740991
          },
          "app_id": {
            "type": "integer",
            "minimum": 0,
            "exclusiveMinimum": true,
            "maximum": 9007199254740991
          },
          "market_hash_name": {
            "type": "string"
          },
          "price": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "amount": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "commodity": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
          },
          "available_amount": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          }
        },
        "required": [
          "id",
          "app_id",
          "market_hash_name",
          "price",
          "amount",
          "commodity",
          "created_at",
          "available_amount"
        ],
        "additionalProperties": false
      }
    },
    "metadata": {
      "type": "object",
      "properties": {
        "total_pages": {
          "type": "integer",
          "minimum": -9007199254740991,
          "maximum": 9007199254740991
        },
        "total_items": {
          "type": "integer",
          "minimum": -9007199254740991,
          "maximum": 9007199254740991
        },
        "current_page": {
          "type": "integer",
          "minimum": -9007199254740991,
          "maximum": 9007199254740991
        },
        "current_limit": {
          "type": "integer",
          "minimum": -9007199254740991,
          "maximum": 9007199254740991
        }
      },
      "required": [
        "total_pages",
        "total_items",
        "current_page",
        "current_limit"
      ],
      "additionalProperties": false
    }
  },
  "required": [
    "listings",
    "metadata"
  ],
  "additionalProperties": false
}
```

## Value of your listings

`GET /public/v1/my-listings/value`

Requires `Authorization: Bearer csd_...`.

What your active listings are worth at their asking prices: `total_value` in cents, across `listing_count` listings holding `item_count` items. Optional `app_id` filter. The v1 equivalent was `ISales/GetActiveListingsValue`.

**Parameters**

| Name | In | Required | Type | Description |
| --- | --- | --- | --- | --- |
| `app_id` | query | no | string, matches ^[1-9]\d*$ |  |

**Response 200**: 

```json
{
  "type": "object",
  "properties": {
    "listing_count": {
      "type": "integer",
      "minimum": -9007199254740991,
      "maximum": 9007199254740991
    },
    "item_count": {
      "type": "integer",
      "minimum": -9007199254740991,
      "maximum": 9007199254740991
    },
    "total_value": {
      "type": "integer",
      "minimum": -9007199254740991,
      "maximum": 9007199254740991
    }
  },
  "required": [
    "listing_count",
    "item_count",
    "total_value"
  ],
  "additionalProperties": false
}
```
