API reference
Read-only HTTPS access to the catalog, prices, listings and your own orders and wallet, plus signed webhooks for what happens on your account.
Overview
Every endpoint is a GET under this base URL and returns JSON. Money is in USD, as decimal strings. The OpenAPI document describes every field.
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 → Security.
curl https://api.cs2sell.com/v1/public/me \ -H "Authorization: Bearer sk_…"
{
"api_key_id": "6f1c2d3e-…",
"user_id": "0b7e9a41-…",
"tier": "free",
"scopes": ["catalog:read", "orders:read"],
"rate_limit_per_minute": 60
}Endpoints
The scope each endpoint needs is in the middle column.
| Endpoint | Scope | Returns |
|---|---|---|
| GET /v1/public/me | any key | The key you're calling with: tier, scopes and rate limit. |
| GET /v1/public/catalog | catalog:read | Search the item catalog.paged |
| GET /v1/public/items/{id} | catalog:read | One item with its variants. |
| GET /v1/public/prices/{variant_id} | catalog:read | A variant's prices across marketplaces. |
| GET /v1/public/marketplaces | catalog:read | The marketplaces we track prices on. |
| GET /v1/public/listings | listings:read | Active listings on CS2Sell, with filters.paged |
| GET /v1/public/orders | orders:read | Your orders.paged |
| GET /v1/public/wallet/balance | wallet:read | Your available and pending balance. |
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
Each key has a per-minute budget for every scope separately, so polling the catalog doesn't eat into your order checks. Every response says where you stand.
| Tier | Requests per minute, per scope | |
|---|---|---|
| Free | 60 | Available |
| Pro | 600 | Coming soon |
| Business | 3000 | Coming soon |
- 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 403 Forbidden
{
"error": {
"code": "api_keys.scope_missing",
"params": { "scope": "orders:read" }
}
}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 → Security.
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
- trade.completed
- A bot or P2P trade finished
- trade.reversed
- A trade was reversed on Steam
- 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=ca488ead1f388f2edbd01f592eeb29b9c27225c76da89e2e78a4abb6ca451c6e {"id":"0b9c6f2e-4f7a-4c55-9a43-2d6f1f0e8a11","event":"listing.sold","created_at":"2026-10-08T12:00:00Z","data":{"listing_id":"5d1b2a90-7c3e-4b8e-9f0a-6a2c4e8d1f37","order_id":"c2e4f6a8-1b3d-4f5a-8c7e-9d0b2a4c6e81","price_usd":"42.50","kind":"p2p"}}
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.
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.tier, 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()["tier"], 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 {
Tier string `json:"tier"`
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))
}