API

Selling items

List backpack items on the market, reprice them, and take them down.

Open raw markdownFor pasting into AI assistants

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

  • Still in Steam: POST /public/v1/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. 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

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

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

One row is one Steam stack, and amount is how many copies that row's token covers. A commodity you hold several stacks of comes back as several rows, each with its own token, so never send an amount larger than the row you took the token from. Doing so fails with INSUFFICIENT_ITEM_AMOUNT.

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

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 }] }'

To sell several stacks of the same item as one listing, send one entry per token in the same items array:

  -d '{ "listings": [{ "items": [
        { "token": "eyJ...stack A...", "amount": 60 },
        { "token": "eyJ...stack B...", "amount": 1 }
      ], "price": 12 }] }'

A request may carry up to 50 listing groups with up to 50 entries each, but those are limits on the shape of the body, not on how much you can trade. One request becomes one trade offer per game, and a single offer may carry at most 500 copies in total, counting amount rather than entries. Over that the call fails with DEPOSIT_ITEM_LIMIT and nothing is created. You can also have at most 15 trades in flight at once, past which it fails with ACTIVE_TRADE_LIMIT.

You get back deposit_ids. Each one appears as deposit_id on a GET /public/v1/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

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

GET /public/v1/backpack returns what you own:

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 you bought here with trade_locked_until set can still be listed; it just cannot leave the platform until the hold expires.

An item you deposited yourself is different. It stays in Steam's trade protected context until the hold clears, and listing it before then fails with ITEM_TRADE_LOCKED. So a bot that deposits and immediately lists should either wait for trade_locked_until to pass or retry on that error.

For a price to ask, GET /public/v1/prices/all gives market_price and recommended_price for every item in one cached response.

2. List it

POST /public/v1/list takes groups of items, each with one price in cents:

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 }] }'
{
  "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:

# 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

PATCH /public/v1/list changes a listing. Send price, amount, or both:

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:

# 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. Sending price is what decides this, not whether the value changed: a price updater that re-sends the price a listing already has still gets a split, never a return. To pull copies back to your backpack, send amount on its own.

If you only want to change the price, leave amount out. { "listing_id": 55120, "price": 950 } reprices the whole stack in place however many copies it holds, with no split and nothing returned. That is what a price updater wants. The response is the listing you edited; pick up the new one from GET /public/v1/my-listings.

Auto-decaying prices

Send price_decay instead of price, on a listing group or on an edit, to let the price fall on its own: it starts at start_price and reaches end_price after total_hours, which is between 24 and 168. end_price must be below start_price.

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_decay": { "start_price": 4250, "end_price": 3000, "total_hours": 48 }
      }] }'

A decaying listing behaves like any other everywhere else, the partial reprice above included, and its price is always the price right now. GET /public/v1/my-listings carries the curve and how far into it the listing is in price_decay, or null when the price is flat. Send a plain price to put a decaying listing back on a flat one.

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:

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:

{
  "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

POST /public/v1/delist removes a listing and returns its items to your backpack:

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:

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

GET /public/v1/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 can add to the listing. Copies still inside a Steam trade hold are excluded, since those cannot be listed yet.

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 answers "what am I currently asking for, in total" in one call:

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

When something sells

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

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 hands back your whole history in one file, CSV by default:

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.