x-api-key and x-merchant-id are required for every request.
BuyCS2 API Documentation
Browse inventory, buy CS2 items, track trades, manage wallet balance, and receive webhook updates in a clearer documentation interface.
Use GET /Items/GetAllItemsOnSale to sync your sale inventory.
All prices and balances use cents, for example 1500 = $15.00.
Choose webhook mode or polling-only mode for trade updates. GET /Trades/History supports polling.
Authentication
All API requests require 2 headers:
| Header | Description |
|---|---|
x-api-key | Your merchant API Key |
x-merchant-id | Your Merchant ID |
{ "error": "unauthorized", "message": "Invalid API key or Merchant ID" }
{ "error": "service_unavailable", "message": "Server is still initializing, please try again in a minute" }
{ "error": "maintenance", "message": "System is under maintenance. Please try again later." }
{ "error": "temporary_maintenance", "message": "Temporary maintenance, retry in 5 minutes" }
You can find your API key and Merchant ID in the Merchant Portal under Settings.
Base URL
api.buycs2.com
All endpoints described below are relative to this base URL.
Compression
All responses from the server are compressed using gzip. Your HTTP client must support decompression:
Accept-Encoding: gzip
Rate Limits
| Endpoint | Limit |
|---|---|
GET /Items/GetAllItemsOnSale | 10 requests per minute |
| All other endpoints | 120 requests per minute |
Exceeding the rate limit returns HTTP 429.
Currency
All prices and balances are in USD cents (1 USD = 100 cents). For example, 1500 = $15.00.
Timestamps
All timestamps returned by the API are in UTC using ISO 8601 format:
"2025-01-15T12:30:45.000Z"
The trailing Z indicates UTC. Convert to your local timezone on the client side.
Error Handling
Error responses include a JSON body with error and message fields:
{
"error": "invalid_request",
"message": "Missing required fields: itemId, assetId, price, tradeUrl"
}
| Status | Meaning |
|---|---|
200 | Success |
400 | Bad request β invalid parameters |
401 | Unauthorized β invalid or missing API key / Merchant ID |
403 | Forbidden β partner blocked or too many active trades |
429 | Rate limited β too many requests |
500 | Internal server error |
503 | Service unavailable β server initializing, maintenance, or temporary maintenance |
Overall Flow
Inventory sync is handled separately via GET /Items/GetAllItemsOnSale. For trade updates, choose one of the following modes.
Webhook Mode
Merchant BuyCS2
| |
|--- POST /Items/Buy ---------------------------->| Buy item
|<-- { tradeId } ---------------------------------|
|<-- webhook: PENDING / SENT / COMPLETED / ... ---| Each status once
| |
|--- GET /Trades/History?webhookStatus=failed --->| Catch missed (cron)
Polling-Only Mode
Merchant BuyCS2
| |
|--- POST /Items/Buy ---------------------------->| Buy item
|<-- { tradeId } ---------------------------------|
| |
|--- GET /Trades/History?isActive=true ---------->| Poll all active trades
|<-- { trades: [...] } ---------------------------| Map & compare locally
Get All Items on Sale
/Items/GetAllItemsOnSale
Get all available items for sale. Use this endpoint for inventory sync.
Response
{
"totalItems": 1,
"totalValue": 1500,
"items": [
{
"itemId": 123,
"assetId": "12345678901",
"classId": "310776585",
"instanceId": "480085569",
"paintIndex": 12,
"paintSeed": 225,
"rarityColor": "#eb4b4b",
"market_hash_name": "AK-47 | Redline (Field-Tested)",
"img": "https://steamcdn-a.akamaihd.net/apps/730/icons/...",
"phase": null,
"price": 1500,
"float": 0.15234,
"stickers": [
{
"name": "Sticker | Natus Vincere | Katowice 2014",
"img": "https://cdn.steamstatic.com/apps/730/icons/econ/stickers/katowice2014/navi.png",
"slot": 0,
"wear": "90%",
"rotation": 45,
"offsetX": 0.13,
"offsetY": 0.19
}
],
"keyChains": [
{
"name": "Charm | Snowman",
"img": "https://cdn.steamstatic.com/apps/730/icons/econ/charms/charm_snowman.png",
"slot": 0,
"pattern": 123,
"offsetX": -0.15,
"offsetY": 0.07,
"offsetZ": 1.78
}
],
"steamId": "76561198012345678",
"inspectInGame": "steam://rungame/730/..."
}
]
}
| Field | Type | Description |
|---|---|---|
totalItems | integer | Total number of items |
totalValue | integer | Total value of all items (USD cents) |
itemId | integer | Item ID (used for buy endpoint) |
assetId | string | Steam Asset ID |
classId | string | Steam Class ID |
instanceId | string | Steam Instance ID |
paintIndex | integer|null | Paint index (skin ID). 0 = vanilla, null = no data |
paintSeed | integer|null | Paint seed (pattern seed, 0β999). 0 = vanilla, null = no data |
rarityColor | string|null | Rarity color hex code: #b0c3d9, #5e98d9, #4b69ff, #8847ff, #d32ce6, #eb4b4b, #e4ae39. null = no data |
market_hash_name | string | Full CS2 market hash name |
img | string|null | Item image URL (null if not available) |
phase | string|null | Doppler phase (null if not Doppler) |
price | integer | Selling price (USD cents) |
float | number|null | Float value (null if not available) |
stickers | array | Sticker/Patch list (name, img, slot, wear, rotation?, offsetX?, offsetY?). wear is a percentage string such as "100%" or null when inspect data is unavailable. rotation/offset* are optional and only present for custom placement. slot may not be unique in CS2; use array order if you need stable identification |
keyChains | array | Attached item list (name, img, slot, pattern?, rotation?, offsetX?, offsetY?, offsetZ?). This array can contain standard charms, souvenir charms, and sticker slabs. Optional fields are only present when available. slot may not be unique in CS2 |
steamId | string|null | Steam ID of the account holding this item (null if not enabled). Contact admin to enable |
inspectInGame | string|null | In-game inspect link (null if not enabled). Contact admin to enable |
Error Responses
| HTTP | Error Code | Description |
|---|---|---|
503 | temporary_maintenance | Temporary maintenance, retry in 5 minutes |
Search Items by Name
/Items/SearchItems
Search items by name instead of fetching the full inventory list.
Request Body
{
"market_hash_name": [
"AK-47 | Panthera onca (Minimal Wear)",
"β
StatTrakβ’ Paracord Knife | Case Hardened (Well-Worn)"
]
}
| Field | Type | Required | Description |
|---|---|---|---|
market_hash_name | string[] | Yes | Array of market hash names to search (max 10) |
Response
Same format as Get All Items β returns only matching items.
Error Responses
| HTTP | Error Code | Description |
|---|---|---|
400 | invalid_request | Missing or invalid market_hash_name array |
503 | temporary_maintenance | Temporary maintenance, retry in 5 minutes |
Get Balance
/Wallet/GetBalance
Check merchant's current balance, credit limit, and total available spending power.
Response
{
"balance": 500000,
"credit_limit": 10000,
"total_available": 510000
}
| Field | Type | Description |
|---|---|---|
balance | integer | Current balance (USD cents). Can be negative if credit is in use |
credit_limit | integer | Credit limit granted by admin (USD cents). Default 0 |
total_available | integer | Total spending power = balance + credit_limit (USD cents) |
Balance History
/Wallet/BalanceHistory
Query merchant's balance transaction history. All params are optional.
Query Parameters
| Param | Type | Required | Description |
|---|---|---|---|
type | string | No | Filter by type (see transaction types below) |
tradeId | string | No | Filter by exact trade ID |
limit | integer | No | Results per page (default 50, max 500) |
page | integer | No | Page number (default 1) |
Transaction Types
| Type | Description |
|---|---|
ADMIN TOP UP | Admin top-up |
BUY COMPLETED | Buy trade completed |
BUY CANCELLED | Buy trade cancelled |
ADMIN DEDUCT | Admin deduction |
TRADE ROLLBACK | Trade rollback refund |
FREEZE | Balance frozen for pending buy |
Response
{
"transactions": [
{
"id": 1,
"type": "BUY COMPLETED",
"amount": -1500,
"balanceBefore": 500000,
"balanceAfter": 498500,
"tradeId": "abc-123-def",
"note": null,
"createdAt": "2025-01-01T12:05:00.000Z",
"market_hash_name": "AK-47 | Redline (Field-Tested)"
}
],
"total": 100,
"limit": 50,
"page": 1
}
Buy Item by ID
/Items/Buy
Purchase an item by ID and send a Steam trade offer to the recipient.
Request Body
{
"itemId": 123,
"assetId": "12345678901",
"price": 1500,
"tradeUrl": "https://steamcommunity.com/tradeoffer/new/?partner=123&token=abc",
"externalId": "order_abc123",
"tradeDurationSeconds": 1800
}
| Field | Type | Required | Description |
|---|---|---|---|
itemId | integer | Yes | Item ID from inventory endpoint |
assetId | string | Yes | Steam Asset ID (must match the item) |
price | integer | Yes | Expected price (USD cents). Rejected if price has changed |
tradeUrl | string | Yes | Recipient's Steam trade URL |
externalId | string | No | Your order ID (must be unique per merchant, or null). Duplicate returns error duplicate_external_id |
tradeDurationSeconds | integer | No | How long (seconds) the recipient has to accept the trade before it auto-cancels. Min: 600 (10 min), Max: 43200 (12h), Default: 1800 (30 min) |
Success Response
{
"tradeId": "abc-123-def",
"tradeUrl": "https://steamcommunity.com/tradeoffer/new/?partner=123&token=abc",
"assetId": "12345678901",
"market_hash_name": "AK-47 | Redline (Field-Tested)",
"phase": null,
"price": 1500,
"externalId": "order_abc123"
}
PENDING status. A PENDING webhook will be sent immediately after.
Error Responses
| HTTP | Error Code | Description |
|---|---|---|
400 | invalid_request | Missing required fields |
400 | item_unavailable | Item is no longer available |
400 | asset_mismatch | assetId does not match the item |
400 | price_changed | Price has changed (message contains new price) |
400 | invalid_tradeurl | Invalid Steam trade URL format |
400 | duplicate_external_id | externalId already used for another trade |
400 | insufficient_balance | Insufficient merchant balance |
400 | invalid_price | Price must be greater than 0 |
400 | invalid_trade_duration | tradeDurationSeconds must be between 600 (10 min) and 43200 (12h) |
403 | partner_blocked | Trade URL owner is temporarily blocked due to repeated declines/expired trades. Includes unblock time in message |
403 | too_many_active_trades | Trade URL owner has too many active trades (max 20 PENDING/SENT/CREATING) |
503 | temporary_maintenance | Temporary maintenance, retry in 5 minutes |
Buy Item by Name
/Items/BuyByName
Purchase an item by name. By default, the system finds the cheapest available item matching the criteria. Optionally, specify itemId and assetId to buy a specific item with maxPrice protection.
Request Body
{
"market_hash_name": "AK-47 | Redline (Field-Tested)",
"phase": null,
"tradeUrl": "https://steamcommunity.com/tradeoffer/new/?partner=123&token=abc",
"maxPrice": 1500,
"externalId": "order_abc123",
"tradeDurationSeconds": 1800,
"itemId": 12345,
"assetId": "50754412249"
}
| Field | Type | Required | Description |
|---|---|---|---|
market_hash_name | string | Yes | Full CS2 market hash name |
phase | string | No | Doppler phase filter (e.g. "Phase 1", "Ruby", "Sapphire"). Only used when itemId is not provided |
tradeUrl | string | Yes | Recipient's Steam trade URL |
maxPrice | integer | Yes | Maximum price willing to pay (USD cents). Rejected if item price exceeds this |
externalId | string | No | Your order ID (must be unique per merchant, or null) |
tradeDurationSeconds | integer | No | How long (seconds) the recipient has to accept the trade before it auto-cancels. Min: 600 (10 min), Max: 43200 (12h), Default: 1800 (30 min) |
itemId | integer | No | Specific item ID to buy. Must be used together with assetId |
assetId | string | No | Steam Asset ID of the item. Must be used together with itemId |
Success Response
Same format as Buy Item by ID.
Error Responses
| HTTP | Error Code | Description |
|---|---|---|
400 | invalid_request | Missing required fields, or only one of itemId/assetId provided |
400 | item_unavailable | No matching item available |
400 | asset_mismatch | assetId does not match the item |
400 | price_too_high | Item price exceeds maxPrice |
400 | invalid_tradeurl | Invalid Steam trade URL format |
400 | duplicate_external_id | externalId already used for another trade |
400 | insufficient_balance | Insufficient merchant balance |
400 | invalid_price | maxPrice must be greater than 0 |
400 | invalid_trade_duration | tradeDurationSeconds must be between 600 (10 min) and 43200 (12h) |
403 | partner_blocked | Trade URL owner is temporarily blocked due to repeated declines/expired trades. Includes unblock time in message |
403 | too_many_active_trades | Trade URL owner has too many active trades (max 20 PENDING/SENT/CREATING) |
503 | temporary_maintenance | Temporary maintenance, retry in 5 minutes |
Trade History
/Trades/History
Query trade history. All params are optional β without any filter, returns the 50 most recent trades.
Query Parameters
| Param | Type | Required | Description |
|---|---|---|---|
tradeId | string | No | Find a specific trade by ID |
status | string | No | Filter by status (PENDING, COMPLETED, ...) |
externalId | string | No | Find by your order ID |
webhookStatus | string | No | Filter by webhook status (sent, failed) |
isActive | string | No | Set to true to return all trades created within the last 10 days (no limit). Useful for polling-only mode |
limit | integer | No | Results per page (default 50, max 500). Ignored when isActive=true |
page | integer | No | Page number (default 1). Ignored when isActive=true |
Response
{
"trades": [
{
"tradeId": "abc-123-def",
"externalId": "order_abc123",
"status": "COMPLETED",
"price": 1500,
"offerId": "5678901234",
"tradeUrl": "https://steamcommunity.com/tradeoffer/new/?partner=123&token=abc",
"market_hash_name": "AK-47 | Redline (Field-Tested)",
"webhookStatus": "sent",
"failReason": null,
"errorCode": null,
"createdAt": "2025-01-01T12:00:00.000Z",
"updatedAt": "2025-01-01T12:05:00.000Z",
"completedAt": "2025-01-01T12:05:00.000Z",
"cancelledAt": null,
"endProtection": "2026-03-17T07:00:00.000Z"
}
],
"total": 150,
"limit": 50,
"page": 1
}
| Field | Type | Description |
|---|---|---|
tradeId | string | Trade ID (from buy endpoint) |
externalId | string | Your order ID sent during purchase |
status | string | Current trade status |
price | integer | Price (USD cents) |
offerId | string | Steam Trade Offer ID (null if not created yet) |
tradeUrl | string | Recipient's Steam trade URL |
market_hash_name | string | CS2 item name |
webhookStatus | string | Webhook delivery status (sent, failed, or null) |
failReason | string | Failure reason (null if successful) |
errorCode | integer | Error code (null if successful) |
createdAt | string | Trade creation time (ISO 8601 UTC) |
updatedAt | string | Last update time (ISO 8601 UTC) |
completedAt | string | Completion time (ISO 8601 UTC, null if not completed) |
cancelledAt | string | Cancellation time (ISO 8601 UTC, null if not cancelled) |
endProtection | string | End protection time (ISO 8601 UTC, null if no data) |
Trade Statuses
Both Webhook Mode and Polling-Only Mode use the same set of trade statuses. These values appear in the trade_updated webhook payload and the GET /Trades/History API response.
| Status | Description |
|---|---|
PENDING | Request received, waiting for trade offer to be created |
SENT | Steam trade offer sent, waiting for user to accept |
COMPLETED | Trade completed β item has been delivered |
CANCELLED | Trade cancelled β user declined, countered, or items sold elsewhere |
FAILED | Trade failed β Steam error, user unavailable, or items invalid |
EXPIRED | Trade offer expired β user did not accept in time |
REVERSED | Completed trade was reversed by Steam (can arrive days later) |
REJECTED | Trade rejected before offer created (provider error). No webhook sent. Balance fully refunded. Only visible in GET /Trades/History |
REJECTED which does not send a webhook (the Buy API already returns an error response).
A COMPLETED trade can still be REVERSED by Steam days later.
Error Codes & Fail Reasons
| Status | errorCode | failReason | Description |
|---|---|---|---|
| COMPLETED | null | null | Item delivered successfully |
| EXPIRED | 503 | Trade offer expired | Buyer didn't accept in time |
| CANCELLED | 504 | Trade declined by user | Buyer declined the trade offer |
| CANCELLED | 504 | Trade countered | Buyer countered the trade offer |
| CANCELLED | 505 | Items no longer valid | Trade offer was sent but the item was sold elsewhere before the buyer accepted |
| FAILED | 508 | User not available to trade | Buyer's account cannot trade |
| FAILED | 508 | User not available to trade (15) | Buyer's account cannot trade (Steam error 15) |
| FAILED | 505 | Items no longer valid | Item not found in seller's inventory |
| FAILED | 500 | Can't create trade offer because account's proxy network error | Seller network temporary error |
| FAILED | 500 | (raw error message from Steam) | General Steam error not matching any pattern above |
| REVERSED | 512 | Completed trade was reversed by Steam | Buyer rolled back the trade (can arrive days later) |
errorCode: 500 without a specific mapped reason, the failReason will contain the raw error message from Steam.
Webhooks
When a trade status changes or an item is removed from inventory, a POST request is sent to the merchant's configured webhook URL.
Webhook Authentication
| Header | Value |
|---|---|
x-signature | HMAC-SHA256 of JSON body using API Key as secret |
x-webhook-type | trade_updated or item_removed |
Content-Type | application/json |
Verify Signature
expected = HMAC_SHA256(key=api_key, message=JSON.stringify(body))
valid = (expected === headers['x-signature'])
Webhook: trade_updated
{
"tradeId": "abc-123-def",
"status": "COMPLETED",
"externalId": "order_abc123",
"offerId": "5678901234",
"failReason": null,
"errorCode": null
}
| Field | Type | Description |
|---|---|---|
tradeId | string | Trade ID (from buy endpoint) |
status | string | Current status |
externalId | string | Your order ID sent during purchase |
offerId | string | Steam Trade Offer ID (null if not created yet) |
failReason | string | Failure reason (null if successful) |
errorCode | integer | Error code (null if successful) |
status, errorCode, and failReason values.
Webhook: item_removed
Sent when an item is removed from inventory. Helps merchants update their cache immediately instead of waiting for the next poll.
{ "itemId": 123 }
Circuit Breaker
To protect system performance, BuyCS2 uses dual circuit breakers β separate thresholds for trade_updated and item_removed events. If your endpoint fails to respond (timeout, network error, or non-2xx status) consecutively, webhook delivery will be temporarily paused per event type.
| Parameter | Value |
|---|---|
trade_updated threshold | 15 consecutive failures |
item_removed threshold | 20 consecutive failures |
| Initial cooldown | 15 minutes |
| Cooldown escalation | Doubles each cycle (15m β 30m β 1h β 2h β 4h max) |
| Max cooldown | 4 hours |
item_removed failures will never block trade_updated delivery. However, a successful webhook of either type resets both circuits.
- Go to Merchant Portal β Settings
- Click Test Webhook β this sends a
testevent to your endpoint - Your server must return HTTP 2xx for the test event
- On success, the circuit breaker resets and normal webhook delivery resumes immediately
{
"event": "test",
"message": "Webhook test from BuyCS2 Portal",
"timestamp": "2025-01-15T10:30:00.000Z"
}
The test webhook is sent with header x-webhook-type: test. Make sure your server accepts this event type and returns 200 OK.