Developers

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.

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

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.

EndpointScopeReturns
GET /v1/public/meany keyThe key you're calling with: tier, 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/ordersorders:readYour orders.paged
GET /v1/public/wallet/balancewallet:readYour 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.

TierRequests per minute, per scope
Free60Available
Pro600Coming soon
Business3000Coming 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))
}