Merchant API reference

BuyCS2 API Documentation

Browse inventory, buy CS2 items, track trades, manage wallet balance, and receive webhook updates in a clearer documentation interface.

Request API access
Authentication 2 required headers

x-api-key and x-merchant-id are required for every request.

Inventory sync 10 req / minute

Use GET /Items/GetAllItemsOnSale to sync your sale inventory.

Merchant balance USD cents

All prices and balances use cents, for example 1500 = $15.00.

Realtime updates Webhook or polling

Choose webhook mode or polling-only mode for trade updates. GET /Trades/History supports polling.

πŸ’‘ Getting Started: You need a merchant account to use the API. Contact us on Telegram to get started.

Authentication

All API requests require 2 headers:

HeaderDescription
x-api-keyYour merchant API Key
x-merchant-idYour Merchant ID
Auth Error (401)
{ "error": "unauthorized", "message": "Invalid API key or Merchant ID" }
Server Initializing (503)
{ "error": "service_unavailable", "message": "Server is still initializing, please try again in a minute" }
System Maintenance (503)
{ "error": "maintenance", "message": "System is under maintenance. Please try again later." }
Temporary Maintenance (503) β€” GetAllItemsOnSale, SearchItems, Buy, BuyByName
{ "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.

⚠️ Security: Never expose your API key in client-side code. Always make API calls from your server.

Base URL

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:

Required Header
Accept-Encoding: gzip

Rate Limits

EndpointLimit
GET /Items/GetAllItemsOnSale10 requests per minute
All other endpoints120 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:

Timestamp 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 Response Example
{
  "error": "invalid_request",
  "message": "Missing required fields: itemId, assetId, price, tradeUrl"
}
StatusMeaning
200Success
400Bad request β€” invalid parameters
401Unauthorized β€” invalid or missing API key / Merchant ID
403Forbidden β€” partner blocked or too many active trades
429Rate limited β€” too many requests
500Internal server error
503Service 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

Webhook Flow
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

Polling Flow
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

GET /Items/GetAllItemsOnSale

Get all available items for sale. Use this endpoint for inventory sync.

⚠️ Rate limit: 10 requests per minute.

Response

200 OK
{
  "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/..."
    }
  ]
}
FieldTypeDescription
totalItemsintegerTotal number of items
totalValueintegerTotal value of all items (USD cents)
itemIdintegerItem ID (used for buy endpoint)
assetIdstringSteam Asset ID
classIdstringSteam Class ID
instanceIdstringSteam Instance ID
paintIndexinteger|nullPaint index (skin ID). 0 = vanilla, null = no data
paintSeedinteger|nullPaint seed (pattern seed, 0–999). 0 = vanilla, null = no data
rarityColorstring|nullRarity color hex code: #b0c3d9, #5e98d9, #4b69ff, #8847ff, #d32ce6, #eb4b4b, #e4ae39. null = no data
market_hash_namestringFull CS2 market hash name
imgstring|nullItem image URL (null if not available)
phasestring|nullDoppler phase (null if not Doppler)
priceintegerSelling price (USD cents)
floatnumber|nullFloat value (null if not available)
stickersarraySticker/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
keyChainsarrayAttached 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
steamIdstring|nullSteam ID of the account holding this item (null if not enabled). Contact admin to enable
inspectInGamestring|nullIn-game inspect link (null if not enabled). Contact admin to enable
πŸ“Œ Note: Prices are in USD cents (e.g., 1500 = $15.00).

Error Responses

HTTPError CodeDescription
503temporary_maintenanceTemporary maintenance, retry in 5 minutes

Search Items by Name

POST /Items/SearchItems

Search items by name instead of fetching the full inventory list.

Request Body

JSON
{
  "market_hash_name": [
    "AK-47 | Panthera onca (Minimal Wear)",
    "β˜… StatTrakβ„’ Paracord Knife | Case Hardened (Well-Worn)"
  ]
}
FieldTypeRequiredDescription
market_hash_namestring[]YesArray of market hash names to search (max 10)

Response

Same format as Get All Items β€” returns only matching items.

Error Responses

HTTPError CodeDescription
400invalid_requestMissing or invalid market_hash_name array
503temporary_maintenanceTemporary maintenance, retry in 5 minutes

Get Balance

GET /Wallet/GetBalance

Check merchant's current balance, credit limit, and total available spending power.

Response

200 OK
{
  "balance": 500000,
  "credit_limit": 10000,
  "total_available": 510000
}
FieldTypeDescription
balanceintegerCurrent balance (USD cents). Can be negative if credit is in use
credit_limitintegerCredit limit granted by admin (USD cents). Default 0
total_availableintegerTotal spending power = balance + credit_limit (USD cents)

Balance History

GET /Wallet/BalanceHistory

Query merchant's balance transaction history. All params are optional.

Query Parameters

ParamTypeRequiredDescription
typestringNoFilter by type (see transaction types below)
tradeIdstringNoFilter by exact trade ID
limitintegerNoResults per page (default 50, max 500)
pageintegerNoPage number (default 1)

Transaction Types

TypeDescription
ADMIN TOP UPAdmin top-up
BUY COMPLETEDBuy trade completed
BUY CANCELLEDBuy trade cancelled
ADMIN DEDUCTAdmin deduction
TRADE ROLLBACKTrade rollback refund
FREEZEBalance frozen for pending buy

Response

200 OK
{
  "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

POST /Items/Buy

Purchase an item by ID and send a Steam trade offer to the recipient.

Request Body

JSON
{
  "itemId": 123,
  "assetId": "12345678901",
  "price": 1500,
  "tradeUrl": "https://steamcommunity.com/tradeoffer/new/?partner=123&token=abc",
  "externalId": "order_abc123",
  "tradeDurationSeconds": 1800
}
FieldTypeRequiredDescription
itemIdintegerYesItem ID from inventory endpoint
assetIdstringYesSteam Asset ID (must match the item)
priceintegerYesExpected price (USD cents). Rejected if price has changed
tradeUrlstringYesRecipient's Steam trade URL
externalIdstringNoYour order ID (must be unique per merchant, or null). Duplicate returns error duplicate_external_id
tradeDurationSecondsintegerNoHow 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

200 OK
{
  "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"
}
πŸ“Œ Note: A successful response means the trade is already in PENDING status. A PENDING webhook will be sent immediately after.

Error Responses

HTTPError CodeDescription
400invalid_requestMissing required fields
400item_unavailableItem is no longer available
400asset_mismatchassetId does not match the item
400price_changedPrice has changed (message contains new price)
400invalid_tradeurlInvalid Steam trade URL format
400duplicate_external_idexternalId already used for another trade
400insufficient_balanceInsufficient merchant balance
400invalid_pricePrice must be greater than 0
400invalid_trade_durationtradeDurationSeconds must be between 600 (10 min) and 43200 (12h)
403partner_blockedTrade URL owner is temporarily blocked due to repeated declines/expired trades. Includes unblock time in message
403too_many_active_tradesTrade URL owner has too many active trades (max 20 PENDING/SENT/CREATING)
503temporary_maintenanceTemporary maintenance, retry in 5 minutes

Buy Item by Name

POST /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

JSON
{
  "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"
}
FieldTypeRequiredDescription
market_hash_namestringYesFull CS2 market hash name
phasestringNoDoppler phase filter (e.g. "Phase 1", "Ruby", "Sapphire"). Only used when itemId is not provided
tradeUrlstringYesRecipient's Steam trade URL
maxPriceintegerYesMaximum price willing to pay (USD cents). Rejected if item price exceeds this
externalIdstringNoYour order ID (must be unique per merchant, or null)
tradeDurationSecondsintegerNoHow long (seconds) the recipient has to accept the trade before it auto-cancels. Min: 600 (10 min), Max: 43200 (12h), Default: 1800 (30 min)
itemIdintegerNoSpecific item ID to buy. Must be used together with assetId
assetIdstringNoSteam Asset ID of the item. Must be used together with itemId

Success Response

Same format as Buy Item by ID.

Error Responses

HTTPError CodeDescription
400invalid_requestMissing required fields, or only one of itemId/assetId provided
400item_unavailableNo matching item available
400asset_mismatchassetId does not match the item
400price_too_highItem price exceeds maxPrice
400invalid_tradeurlInvalid Steam trade URL format
400duplicate_external_idexternalId already used for another trade
400insufficient_balanceInsufficient merchant balance
400invalid_pricemaxPrice must be greater than 0
400invalid_trade_durationtradeDurationSeconds must be between 600 (10 min) and 43200 (12h)
403partner_blockedTrade URL owner is temporarily blocked due to repeated declines/expired trades. Includes unblock time in message
403too_many_active_tradesTrade URL owner has too many active trades (max 20 PENDING/SENT/CREATING)
503temporary_maintenanceTemporary maintenance, retry in 5 minutes

Trade History

GET /Trades/History

Query trade history. All params are optional β€” without any filter, returns the 50 most recent trades.

Query Parameters

ParamTypeRequiredDescription
tradeIdstringNoFind a specific trade by ID
statusstringNoFilter by status (PENDING, COMPLETED, ...)
externalIdstringNoFind by your order ID
webhookStatusstringNoFilter by webhook status (sent, failed)
isActivestringNoSet to true to return all trades created within the last 10 days (no limit). Useful for polling-only mode
limitintegerNoResults per page (default 50, max 500). Ignored when isActive=true
pageintegerNoPage number (default 1). Ignored when isActive=true

Response

200 OK
{
  "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
}
FieldTypeDescription
tradeIdstringTrade ID (from buy endpoint)
externalIdstringYour order ID sent during purchase
statusstringCurrent trade status
priceintegerPrice (USD cents)
offerIdstringSteam Trade Offer ID (null if not created yet)
tradeUrlstringRecipient's Steam trade URL
market_hash_namestringCS2 item name
webhookStatusstringWebhook delivery status (sent, failed, or null)
failReasonstringFailure reason (null if successful)
errorCodeintegerError code (null if successful)
createdAtstringTrade creation time (ISO 8601 UTC)
updatedAtstringLast update time (ISO 8601 UTC)
completedAtstringCompletion time (ISO 8601 UTC, null if not completed)
cancelledAtstringCancellation time (ISO 8601 UTC, null if not cancelled)
endProtectionstringEnd 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.

StatusDescription
PENDINGRequest received, waiting for trade offer to be created
SENTSteam trade offer sent, waiting for user to accept
COMPLETEDTrade completed β€” item has been delivered
CANCELLEDTrade cancelled β€” user declined, countered, or items sold elsewhere
FAILEDTrade failed β€” Steam error, user unavailable, or items invalid
EXPIREDTrade offer expired β€” user did not accept in time
REVERSEDCompleted trade was reversed by Steam (can arrive days later)
REJECTEDTrade rejected before offer created (provider error). No webhook sent. Balance fully refunded. Only visible in GET /Trades/History
πŸ“Œ Note: Each status triggers a webhook exactly once β€” except 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.
πŸ’Έ Refund note: If a completed trade is reversed by buyer on Steam, the merchant will be refunded 80% of the trade value.

Error Codes & Fail Reasons

StatuserrorCodefailReasonDescription
COMPLETEDnullnullItem delivered successfully
EXPIRED503Trade offer expiredBuyer didn't accept in time
CANCELLED504Trade declined by userBuyer declined the trade offer
CANCELLED504Trade counteredBuyer countered the trade offer
CANCELLED505Items no longer validTrade offer was sent but the item was sold elsewhere before the buyer accepted
FAILED508User not available to tradeBuyer's account cannot trade
FAILED508User not available to trade (15)Buyer's account cannot trade (Steam error 15)
FAILED505Items no longer validItem not found in seller's inventory
FAILED500Can't create trade offer because account's proxy network errorSeller network temporary error
FAILED500(raw error message from Steam)General Steam error not matching any pattern above
REVERSED512Completed trade was reversed by SteamBuyer rolled back the trade (can arrive days later)
πŸ“Œ Note: For 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

HeaderValue
x-signatureHMAC-SHA256 of JSON body using API Key as secret
x-webhook-typetrade_updated or item_removed
Content-Typeapplication/json

Verify Signature

Pseudo-code
expected = HMAC_SHA256(key=api_key, message=JSON.stringify(body))
valid = (expected === headers['x-signature'])

Webhook: trade_updated

Payload
{
  "tradeId": "abc-123-def",
  "status": "COMPLETED",
  "externalId": "order_abc123",
  "offerId": "5678901234",
  "failReason": null,
  "errorCode": null
}
FieldTypeDescription
tradeIdstringTrade ID (from buy endpoint)
statusstringCurrent status
externalIdstringYour order ID sent during purchase
offerIdstringSteam Trade Offer ID (null if not created yet)
failReasonstringFailure reason (null if successful)
errorCodeintegerError code (null if successful)
πŸ“Œ Reference: See Trade Statuses and Error Codes & Fail Reasons for all possible 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.

Payload
{ "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.

ParameterValue
trade_updated threshold15 consecutive failures
item_removed threshold20 consecutive failures
Initial cooldown15 minutes
Cooldown escalationDoubles each cycle (15m β†’ 30m β†’ 1h β†’ 2h β†’ 4h max)
Max cooldown4 hours
Note: The two circuits are independent β€” item_removed failures will never block trade_updated delivery. However, a successful webhook of either type resets both circuits.
How to re-enable webhooks:
  1. Go to Merchant Portal β†’ Settings
  2. Click Test Webhook β€” this sends a test event to your endpoint
  3. Your server must return HTTP 2xx for the test event
  4. On success, the circuit breaker resets and normal webhook delivery resumes immediately
Test Webhook Payload
{
  "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.

πŸ’‘ Tip: Updating your Webhook URL in the Portal also resets the circuit breaker automatically.