API

Selling items

List items on the market, reprice them, take them down, or instant sell them to us.

Open raw markdownFor pasting into AI assistants

There are two ways to list an item, depending on where it 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.

If you would rather not wait for a buyer, you can also instant sell items from Steam to us at a fixed price.

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

Every listing holds exactly one copy, so each copy you sell becomes its own listing at the group's price. To sell several stacks of the same item at one price, 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.

Instant selling from Steam

Instant sell skips the market. We buy the items from you at a fixed price, and once you accept our trade offer the payout goes to your balance. Only Rust is supported for now.

Before you start, your account needs a linked Steam account and a trade URL, and your Steam inventory must be public.

1. Get your inventory and prices

GET /public/v1/instant-sell/inventory?app_id=252490 reads your live Steam inventory and prices it for instant sell. app_id must be 252490 (Rust); anything else fails with BAD_REQUEST.

curl "https://api.cs.deals/public/v1/instant-sell/inventory?app_id=252490" \
  -H "Authorization: Bearer csd_..."
{
  "items": [
    {
      "token": "eyJ...",
      "app_id": 252490,
      "steam_asset_id": "5012345678901234567",
      "market_hash_name": "Sheet Metal Door",
      "amount": 3,
      "commodity": true,
      "tradable": true,
      "instant_sell_price": 720,
      "available_stock": 7,
      "disabled_reason": null,
      "icon_url": "https://..."
    }
  ]
}
FieldMeaning
tokenSigned handle for this stack. Send it to POST /public/v1/instant-sell. Expires after 30 minutes
amountHow many copies this row's token covers. One row is one Steam stack
instant_sell_priceWhat we pay per copy right now, in cents
available_stockHow many more copies of this item we will take. null when we have no price for the item
disabled_reasonnull if the row can be sold. Otherwise why not: NOT_TRADABLE (Steam is holding the item), DISABLED (we are not buying this item), OVERSTOCKED (we already hold enough) or UNSTABLE_PRICE (we have no reliable price for it right now)

Skip rows with a disabled_reason. This call reads live from Steam, so it allows only 5 calls a minute: fetch once, then sell.

Instant sell tokens and the tokens from GET /public/v1/steam-inventory are not interchangeable. Sending the wrong kind fails with INVALID_INVENTORY_TOKEN.

2. Sell

Send the tokens to POST /public/v1/instant-sell with how many copies of each to sell:

curl -X POST "https://api.cs.deals/public/v1/instant-sell" \
  -H "Authorization: Bearer csd_..." \
  -H "Content-Type: application/json" \
  -d '{ "items": [{ "token": "eyJ...", "amount": 3 }] }'
{
  "instant_sell_id": 4120,
  "deposit_id": 98231,
  "status": "TRADE_PENDING",
  "payout": 2160,
  "method": "balance",
  "created_at": "2026-10-15T12:00:00.000Z"
}

payout is the total you will be paid, in cents, and it is fixed at this point. Prices can move between fetching the inventory and selling, so use payout from this response rather than adding up instant_sell_price yourself. method is always balance for sales made through the API.

amount can be at most the row's amount. To sell several stacks of the same item, send one entry per token; sending the same token twice fails with DUPLICATE_ITEM_PAIRS. One request becomes one trade offer, and an offer may carry at most 500 copies in total.

3. Accept the trade offer

We send you a Steam trade offer for the items. Accept it within 5 minutes, otherwise it is cancelled and the sale fails. The offer also appears on GET /public/v1/trades under the same deposit_id, with its steam_offer_id.

4. Follow the sale

Poll GET /public/v1/instant-sell/:deposit_id with the deposit_id from step 2. It returns the same shape as the sell response, with the current status:

statusMeaningFinal
TRADE_PENDINGWaiting for you to accept the trade offerNo
TRADE_FAILEDThe offer was declined, expired or cancelled. Nothing changed hands and nothing is paidYes
PAYMENT_PENDINGThe trade went through and your payout is being processed. This can take some timeNo
SUCCESSThe payout is in your balanceYes
PAYMENT_FAILEDThe trade went through but the payout could not be completed. Contact support with the deposit_idYes

Polling every 10 seconds is plenty; the route allows 30 calls a minute. A deposit_id that is not one of your instant sells returns 404 INSTANT_SELL_NOT_FOUND.

Once paid, the payout also shows on GET /public/v1/transactions with action INSTANT_SELL_PAYOUT. To move the money off site, use the usual withdrawal calls.

Errors

Errors from the inventory call:

ErrorStatusWhat to do
BAD_REQUEST400app_id is missing or not 252490
STEAM_ID_NOT_SET400Link a Steam account to your CS.Deals account
STEAM_INVENTORY_PRIVATE400Make your Steam inventory public

Errors from the sell call:

ErrorStatusWhat to do
BAD_REQUEST400The body is malformed, has more than 500 entries, or has fields other than items
TRADE_URL_NOT_SET400Set your Steam trade URL on your account
INVALID_INVENTORY_TOKEN400The token is not an instant sell token, belongs to another account, or was altered. Fetch the inventory again
INVENTORY_TOKEN_EXPIRED400The token is older than 30 minutes. Fetch the inventory again
INSUFFICIENT_ITEM_AMOUNT400An amount is larger than its row's amount
DUPLICATE_ITEM_PAIRS400The same stack appears twice in items
INSTANT_SELL_GAME_NOT_SUPPORTED400An item is not Rust
DEPOSIT_ITEM_LIMIT400More than 500 copies in total. Split the sale
ACTIVE_TRADE_LIMIT400You already have 15 trades in flight. Wait for some to finish
ITEM_NOT_PRICED400We have no price for an item. Leave it out
INSTANT_SELL_DISABLED400We are not buying an item right now. Leave it out
INSTANT_SELL_ITEM_REJECTED400An item cannot be instant sold right now. Leave it out
INSTANT_SELL_STOCK_LIMIT400More copies of an item than its available_stock. Sell fewer
USER_TRADE_SUSPENDED403Your account is suspended from trading
TRADING_INSTANT_SELL_DISABLED503Instant sell is switched off site-wide. Retry later

When a sell call fails, nothing is created and no trade offer is sent.

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 deposited yourself can be listed straight away, even while Steam still holds it in trade protection (trade_locked_until in the future). A bot can deposit and list in one go.

An item you bought here is different while it is still trade protected: you cannot relist it until the protection ends. Listing it fails with ITEM_TRADE_LOCKED; for a commodity, those copies don't count towards what you can list, so asking for them fails with INSUFFICIENT_ITEMS.

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

Every listing holds exactly one copy. Each copy in a group becomes its own listing at the group's price, and the response lists every listing created, so "amount": 3 on a commodity comes back as three listings. A single call can put fifty different items up at fifty different prices.

A commodity backpack row is a quantity, not a single copy, so the same id can appear in several groups to sell copies of one item at different prices:

# three Cloth: two at 10c, one at 9c, three listings in all
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": 2 }], "price": 10 },
        { "items": [{ "id": 8891, "amount": 1 }], "price": 9 }
      ] }'

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 ids. Each is the listing_id later calls use, and the id buyers see in the public book.

3. Reprice it

PATCH /public/v1/list changes the price of a listing. Every listing holds one copy, so price is the only thing an edit changes. amount is not accepted: sending it fails with BAD_REQUEST. Send exactly one of price or price_decay:

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

The response is the listing you edited. To take copies off sale, delist those listings; to sell more copies, list them from your backpack.

Each listing's price can change once every 10 minutes through the API. Changing it sooner fails with 429 LISTING_PRICE_EDIT_TOO_SOON, with the blocked ids in data.listingIds and the wait in data.retryAfterSeconds. Sending the price a listing already has does not count, and neither do auto-decay drops.

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, 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

Two bulk forms, for two jobs.

Send listing_ids to put up to 500 listings on one price. This is the usual case now that every copy is its own listing, for example repricing all your copies of one commodity. Each id may appear once (BAD_REQUEST otherwise). It runs in one transaction: if any id is missing (LISTING_NOT_FOUND), not yours (FORBIDDEN), not active (LISTING_NOT_ACTIVE) or below the minimum price for its item (SELL_PRICE_OUT_OF_RANGE), the call fails with that error and nothing changes. The error does not say which id failed. A listing that sold since you fetched it counts as missing, so on LISTING_NOT_FOUND refetch GET /public/v1/my-listings and retry with the ids that are still there.

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

(Abbreviated: each entry is a full listing object.)

Send a listings array to give up to 50 listings a price each:

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

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

(Abbreviated: listing is a full listing object.)

The rate limit counts calls, not listings, so batching lifts the ceiling well past 30 edits a minute.

4. Take it down

POST /public/v1/delist removes a listing and returns its item 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 500 at once. A bulk delist runs in one transaction: if any id is missing (LISTING_NOT_FOUND) or not yours (FORBIDDEN), the call fails with that error and nothing is removed. As with bulk repricing, the error does not say which id, and a listing that sold in the meantime counts as missing. On success every id you sent comes back in results with ok: true, in the order you sent them, repeats included:

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
  • DISABLED was taken down

Every row is one copy, so a commodity you have many copies of listed appears as many rows. On a commodity, available_amount is how many more copies of that item you could list from your backpack right now; on anything else it is 0.

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.

Listing can also be switched off site-wide. While it is off, listing, repricing and delisting fail with 503 LISTING_DISABLED. Selling from Steam has its own switch and fails with 503 TRADING_SELL_DISABLED. Retry later in both cases.

GET /public/v1/my-listings/value answers "what am I currently asking for, in total" in one call:

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

Every listing holds one copy, so listing_count and item_count are equal.

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.

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.