Selling items
List items on the market, reprice them, take them down, or instant sell them to us.
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://..."
}
]
}| Field | Meaning |
|---|---|
token | Signed handle for this stack. Send it to POST /public/v1/instant-sell. Expires after 30 minutes |
amount | How many copies this row's token covers. One row is one Steam stack |
instant_sell_price | What we pay per copy right now, in cents |
available_stock | How many more copies of this item we will take. null when we have no price for the item |
disabled_reason | null 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:
status | Meaning | Final |
|---|---|---|
TRADE_PENDING | Waiting for you to accept the trade offer | No |
TRADE_FAILED | The offer was declined, expired or cancelled. Nothing changed hands and nothing is paid | Yes |
PAYMENT_PENDING | The trade went through and your payout is being processed. This can take some time | No |
SUCCESS | The payout is in your balance | Yes |
PAYMENT_FAILED | The trade went through but the payout could not be completed. Contact support with the deposit_id | Yes |
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:
| Error | Status | What to do |
|---|---|---|
BAD_REQUEST | 400 | app_id is missing or not 252490 |
STEAM_ID_NOT_SET | 400 | Link a Steam account to your CS.Deals account |
STEAM_INVENTORY_PRIVATE | 400 | Make your Steam inventory public |
Errors from the sell call:
| Error | Status | What to do |
|---|---|---|
BAD_REQUEST | 400 | The body is malformed, has more than 500 entries, or has fields other than items |
TRADE_URL_NOT_SET | 400 | Set your Steam trade URL on your account |
INVALID_INVENTORY_TOKEN | 400 | The token is not an instant sell token, belongs to another account, or was altered. Fetch the inventory again |
INVENTORY_TOKEN_EXPIRED | 400 | The token is older than 30 minutes. Fetch the inventory again |
INSUFFICIENT_ITEM_AMOUNT | 400 | An amount is larger than its row's amount |
DUPLICATE_ITEM_PAIRS | 400 | The same stack appears twice in items |
INSTANT_SELL_GAME_NOT_SUPPORTED | 400 | An item is not Rust |
DEPOSIT_ITEM_LIMIT | 400 | More than 500 copies in total. Split the sale |
ACTIVE_TRADE_LIMIT | 400 | You already have 15 trades in flight. Wait for some to finish |
ITEM_NOT_PRICED | 400 | We have no price for an item. Leave it out |
INSTANT_SELL_DISABLED | 400 | We are not buying an item right now. Leave it out |
INSTANT_SELL_ITEM_REJECTED | 400 | An item cannot be instant sold right now. Leave it out |
INSTANT_SELL_STOCK_LIMIT | 400 | More copies of an item than its available_stock. Sell fewer |
USER_TRADE_SUSPENDED | 403 | Your account is suspended from trading |
TRADING_INSTANT_SELL_DISABLED | 503 | Instant 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:
ACTIVEis on the market nowFILLEDsoldDISABLEDwas 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:
GET /public/v1/ordersshows rows withside: "sold".GET /public/v1/transactionsshows the money arriving, actionSALE, 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.
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.csvEach row carries the commission in fee and what you actually kept in net.