Developers

API reference

HTTPS and JSON access to the catalog, live listings and prices. Buy listings for your customers and ship them as Steam trade offers, trade your own items with our bots, and follow it all with signed webhooks or the MCP server.

Overview

Everything is under this base URL and answers with JSON. Money is in USD, as decimal strings. The OpenAPI document describes every field, and the MCP endpoint offers the same API to AI agents.

BASEhttps://api.cs2sell.com/v1/public
GEThttps://api.cs2sell.com/v1/openapi.jsonOpenAPI 3 document
MCPhttps://api.cs2sell.com/v1/mcp

Authentication

Send your key as a bearer token. Keys start with sk_ and are shown once, when you create them. Each key carries scopes, and every endpoint except /v1/public/me needs one of them. Create a key under Account → Developers.

deposits:write, orders:write, trades:write move money or items. Creating a key with one takes a two-factor code from your account; a key without them can only read.

curl https://api.cs2sell.com/v1/public/me \
  -H "Authorization: Bearer sk_…"
{
  "api_key_id": "6f1c2d3e-…",
  "user_id": "0b7e9a41-…",
  "scopes": ["catalog:read", "listings:read", "orders:read", "orders:write"],
  "rate_limit_per_minute": 120
}

Endpoints

The scope each endpoint needs is in the middle column.

EndpointScopeReturns
GET /v1/public/meany keyThe key you're calling with: scopes and rate limit.
GET /v1/public/catalogcatalog:readSearch the item catalog.paged
GET /v1/public/items/{id}catalog:readOne item with its variants.
GET /v1/public/prices/{variant_id}catalog:readA variant's prices across marketplaces.
GET /v1/public/marketplacescatalog:readThe marketplaces we track prices on.
GET /v1/public/listingslistings:readActive listings on CS2Sell, with filters.paged
GET /v1/public/listings/lowestlistings:readThe cheapest listing and the number listed of every item on sale, each Doppler phase apart. Not paged; refreshed once a minute.
GET /v1/public/listings/stocklistings:readPartner stock you can buy for a trade URL, with floats, stickers, inspect links and the Buy page's filters.paged
POST /v1/public/ordersorders:writeBuy instant listings, shipped to a trade URL.
GET /v1/public/ordersorders:readYour orders, with each item's delivery.paged
GET /v1/public/orders/{id}orders:readOne order and its deliveries.
GET /v1/public/wallet/balancewallet:readYour available and pending balance.
GET /v1/public/inventorytrades:readYour Steam inventory, with what the bots pay per item.
POST /v1/public/inventory/refreshtrades:readRe-read your inventory from Steam.
POST /v1/public/tradestrades:writeTrade with the bots: sell items, take listings.
GET /v1/public/tradestrades:readYour trades with our bots.
GET /v1/public/trades/{id}trades:readOne bot trade.
POST /v1/public/trades/{id}/refreshtrades:writeAsk the bot for the offer's state now.
POST /v1/public/trades/{id}/canceltrades:writeWithdraw an open trade offer.
POST /v1/public/deposits/inventorydeposits:readYour customer's Steam inventory, with what depositing each item pays you.
POST /v1/public/depositsdeposits:writeDeposit your customer's items: a Steam trade offer per item, paid to your balance.
GET /v1/public/depositsdeposits:readYour deposits, with each item's trade.paged
GET /v1/public/deposits/{id}deposits:readOne deposit and its items.
POST /v1/public/deposits/{id}/refreshdeposits:writeAsk for the state of a deposit's open items now.

Buying for customers

Instant listings ship from our stock as a Steam trade offer to any trade URL you name, so you can sell them on your own site. The price comes out of your purchase balance (deposit_usd) first, then your store balance, when you place the order (trade balance, from sales still inside Steam's trade protection, can't pay for API orders); an item that can't be delivered is refunded to the balance it was paid from.

1. Find instant listings

curl "https://api.cs2sell.com/v1/public/listings?q=AK-47%20%7C%20Redline&instant=true&sort=price_asc" \
  -H "Authorization: Bearer sk_…"

2. Place the order

trade_url is your customer's. external_id is your own order id: send the same one again after a timeout and you get the same order back (200) instead of a second purchase. max_total_usd caps what the order may cost, and max_price_usd can cap each item.

curl https://api.cs2sell.com/v1/public/orders \
  -H "Authorization: Bearer sk_…" \
  -H "Content-Type: application/json" \
  -d '{
    "listing_ids": ["5d1b2a90-7c3e-4b8e-9f0a-6a2c4e8d1f37"],
    "trade_url": "https://steamcommunity.com/tradeoffer/new/?partner=99887766&token=AbCdEf12",
    "external_id": "order-1042",
    "max_total_usd": 25.00
  }'
HTTP/1.1 201 Created

{
  "id": "c2e4f6a8-1b3d-4f5a-8c7e-9d0b2a4c6e81",
  "external_id": "order-1042",
  "status": "awaiting_delivery",
  "source": "api",
  "total_usd": "24.15",
  "steam_id": "76561198060153494",
  "items": [{
    "listing_id": "5d1b2a90-7c3e-4b8e-9f0a-6a2c4e8d1f37",
    "market_hash_name": "AK-47 | Redline (Field-Tested)",
    "price_usd": "24.15",
    "delivery": { "state": "queued", "offer_id": null, "offer_url": null, "hold_end_date": null, "reason": null, "refunded_usd": null }
  }]
}

3. Follow the delivery

Each item has its own trade offer. Poll GET /v1/public/orders/{id} or subscribe to the delivery.* webhooks; an item moves through these states:

queued
Paid; the offer is being prepared.
initiated
The item is confirmed for this order.
pending
The trade offer is being created.
active
The trade offer was sent; your customer accepts it in Steam.
hold
Accepted. Steam's trade protection holds the item until hold_end_date.
completed
Delivered.
failed
The offer was declined, expired or failed. Refunded.
canceled
Cancelled before delivery. Refunded.
reverted
Reversed on Steam after delivery. Refunded.

Only keys with orders:write can buy. An order holds up to 20 listings, all of them instant; anything else is refused before any money moves.

Trading with the bots

Sell items from your own Steam inventory to our bots for balance (give), take bot listings (get), or both in one trade. GET /v1/public/inventory shows what the bots pay per item (trade_in_usd). A bot sends a trade offer to your account; accept it in Steam. What you take is paid from your purchase and store balance and held until the trade ends (trade balance can't pay for it over the API); what the bots pay for your items arrives as trade balance. Set min_receive_usd or max_pay_usd so a price change can't surprise you, and external_id to make the call safe to retry.

curl https://api.cs2sell.com/v1/public/trades \
  -H "Authorization: Bearer sk_…" \
  -H "Content-Type: application/json" \
  -d '{
    "give": ["9a3c1e70-…"],
    "external_id": "sell-77",
    "min_receive_usd": 40.00
  }'

A trade is pending_offer, then awaiting_partner once the offer is out, and ends completed, declined, expired, cancelled or failed. The trade.offer_sent, trade.completed and trade.failed webhooks report each step.

Deposits for your customers

For accounts we've enabled for partner deposits: sell your customers' items for your balance. POST /v1/public/deposits/inventory with the customer's trade URL lists their inventory with what each item pays you (payout_usd). POST /v1/public/deposits with the items and the payouts you showed: a lower quote now refuses it (partner_deposits.price_changed), so you can show the customer the new value. We send the customer a Steam trade offer per item. external_id makes the call safe to retry.

curl https://api.cs2sell.com/v1/public/deposits \
  -H "Authorization: Bearer sk_…" \
  -H "Content-Type: application/json" \
  -d '{
    "trade_url": "https://steamcommunity.com/tradeoffer/new/?partner=99887766&token=AbCdEf12",
    "external_id": "deposit-311",
    "items": [{ "asset_id": "27144320525", "price_usd": "38.00" }]
  }'

An item is created, then offer_sent; once the customer accepts it is accepted and its payout is your trade balance while Steam's trade protection runs, then completed and store balance. failed and cancelled pay nothing; reversed (or a failure after the accept) takes the payout back. The deposit.* webhooks report each step; credit your customer at deposit.completed.

Paging

Lists take ?page= (from 1) and ?page_size= (default 50, at most 100) and answer with this envelope:

{
  "items": [ … ],
  "page": 1,
  "page_size": 50,
  "total": 1284
}

Rate limits

Every key gets 120 requests per minute for each scope separately, so polling the catalog doesn't eat into your order checks. Every response says where you stand.

X-RateLimit-Limit
Requests allowed per minute for this scope.
X-RateLimit-Remaining
Requests left in the current minute.
X-RateLimit-Reset
Seconds until the minute resets.
Retry-After
Sent with a 429: seconds to wait before retrying.

Errors

Errors use the HTTP status plus a stable code you can match on. params fills the placeholders of the code's message, and validation errors add a fields list.

HTTP/1.1 409 Conflict

{
  "error": {
    "code": "public_orders.over_max_total",
    "params": { "total": "31.50", "max": "30.00" }
  }
}

Webhooks

Add an endpoint and pick the events you want. Each event is POSTed as JSON and signed with your endpoint's secret (whsec_…). Add one under Account → Developers.

Events

listing.created
One of your listings went live
listing.sold
One of your listings sold
listing.cancelled
One of your listings was cancelled, by you or by us
order.completed
An order of yours was delivered
order.refunded
An order of yours was refunded
delivery.offer_sent
A bought item's Steam trade offer was sent
delivery.hold
A bought item's offer was accepted and is in Steam's trade protection
delivery.completed
A bought item was delivered
delivery.failed
A bought item's offer was declined, expired or failed; refunded
delivery.reversed
A delivered item was reversed on Steam; refunded
trade.offer_sent
A bot sent you a trade offer
trade.completed
A bot or P2P trade finished
trade.failed
A bot trade was declined, expired, cancelled or failed
trade.reversed
A trade was reversed on Steam
deposit.offer_sent
A deposited item's Steam trade offer was sent to your customer
deposit.accepted
Your customer accepted a deposit offer; paid as trade balance during Steam's trade protection
deposit.completed
A deposited item's trade protection ended; paid as store balance
deposit.failed
A deposit offer was declined, expired, cancelled or failed; nothing paid
deposit.reversed
A deposited item was reversed after the accept; its payout was taken back
wallet.deposit
A deposit was credited
wallet.withdrawal
A withdrawal was sent or failed

Headers

X-CS2Sell-Signature
sha256= followed by the hex HMAC-SHA256 of the raw body, keyed with your secret.
X-CS2Sell-Event
The event name, also in the body.
X-CS2Sell-Delivery-Id
Unique per delivery and the same on every retry; use it to drop duplicates.
X-CS2Sell-Timestamp
Unix time this attempt was sent.

Verifying a delivery

Compute the HMAC over the raw body before parsing it, and compare in constant time. With the secret whsec_example, this body signs to:

X-CS2Sell-Signature: sha256=4698a9241a98bcd285a91351115433196163384afd8eb02dc2ad26f7b80c1d16

{"id":"0b9c6f2e-4f7a-4c55-9a43-2d6f1f0e8a11","event":"delivery.completed","created_at":"2026-10-08T12:00:00Z","data":{"order_id":"c2e4f6a8-1b3d-4f5a-8c7e-9d0b2a4c6e81","external_id":"order-1042","listing_id":"5d1b2a90-7c3e-4b8e-9f0a-6a2c4e8d1f37","state":"completed","offer_id":"7100001","offer_url":"https://steamcommunity.com/tradeoffer/7100001/","hold_end_date":null,"reason":null,"refunded_usd":null}}

Answer with any 2xx within 10 seconds. Anything else is retried after 30 seconds, 5 minutes, 30 minutes, 2 hours, 6 hours and 24 hours; after the sixth failure the endpoint is switched off and you're notified. Endpoints must be public http or https URLs, and redirects aren't followed.

Send test on an endpoint under Account → Developers queues a signed ping event, so you can check your receiver and your signature check.

MCP server

The same API is a Model Context Protocol server at /v1/mcp, so AI agents can search listings, buy, and track orders and trades. Add it to your client with one of your keys:

{
  "mcpServers": {
    "cs2sell": {
      "url": "https://api.cs2sell.com/v1/mcp",
      "headers": {
        "Authorization": "Bearer sk_…"
      }
    }
  }
}

It speaks the 2026-07-28 revision and the initialize handshake of 2025-03-26 to 2025-11-25 over Streamable HTTP, answers in JSON and keeps no sessions. tools/list only offers the tools your key's scopes allow.

ToolScopeReturns
search_listingslistings:readSearch active listings; instant ones by default.
search_catalogcatalog:readSearch the item catalog.
get_itemcatalog:readOne catalog item.
get_pricescatalog:readA variant's prices across marketplaces.
get_balancewallet:readYour balance.
list_ordersorders:readYour orders with their deliveries.
get_orderorders:readOne order and each item's trade offer.
buy_listingsorders:writeBuy instant listings with your purchase and store balance and ship them to a trade URL.
get_inventorytrades:readYour inventory with what the bots pay.
refresh_inventorytrades:readRe-read your inventory from Steam.
list_tradestrades:readYour bot trades.
get_tradetrades:readOne bot trade.
create_tradetrades:writeSell items to the bots or take bot listings.
refresh_tradetrades:writeCheck a trade offer's state now.
cancel_tradetrades:writeWithdraw an open trade offer.

Node.js quickstart

There's no SDK to install: the API is plain HTTPS and JSON. Each quickstart reads /v1/public/me and verifies a webhook signature.

// Node.js 18+: fetch and crypto are built in.
import { createHmac, timingSafeEqual } from 'node:crypto';

const res = await fetch('https://api.cs2sell.com/v1/public/me', {
  headers: { Authorization: `Bearer ${process.env.CS2SELL_API_KEY}` },
});
const body = await res.json();
if (!res.ok) throw new Error(body.error.code);
console.log(body.scopes, res.headers.get('x-ratelimit-remaining'));

// Verify a webhook against the raw body, before parsing it.
export function verify(rawBody, signature, secret) {
  const expected = 'sha256=' + createHmac('sha256', secret).update(rawBody).digest('hex');
  const a = Buffer.from(expected);
  const b = Buffer.from(signature ?? '');
  return a.length === b.length && timingSafeEqual(a, b);
}

Python quickstart

# pip install requests
import hashlib, hmac, os
import requests

r = requests.get(
    "https://api.cs2sell.com/v1/public/me",
    headers={"Authorization": f"Bearer {os.environ['CS2SELL_API_KEY']}"},
    timeout=10,
)
r.raise_for_status()
print(r.json()["scopes"], r.headers["X-RateLimit-Remaining"])

def verify(raw_body: bytes, signature: str, secret: str) -> bool:
    """Check X-CS2Sell-Signature against the raw request body."""
    expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature or "")

Go quickstart

package cs2sell

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
)

type Me struct {
	Scopes             []string `json:"scopes"`
	RateLimitPerMinute int      `json:"rate_limit_per_minute"`
}

func FetchMe() (*Me, error) {
	req, err := http.NewRequest("GET", "https://api.cs2sell.com/v1/public/me", nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Authorization", "Bearer "+os.Getenv("CS2SELL_API_KEY"))
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()
	if res.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("cs2sell: %s", res.Status)
	}
	var me Me
	return &me, json.NewDecoder(res.Body).Decode(&me)
}

// Verify checks X-CS2Sell-Signature against the raw request body.
func Verify(body []byte, signature, secret string) bool {
	mac := hmac.New(sha256.New, []byte(secret))
	mac.Write(body)
	expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
	return hmac.Equal([]byte(expected), []byte(signature))
}