BuyCS2 API Documentation

Request API access

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" }

Base URL

Base URL
api.buycs2.com

Compression

Responses are gzip-compressed. Clients must send:

Required Header
Accept-Encoding: gzip

Rate Limits

EndpointLimit
GET /Items/GetAllItemsOnSale10 requests per minute
All other endpoints300 requests per minute

Currency

All prices and balances are in USD cents (1 USD = 100 cents). For example, 1500 = $15.00.

Timestamps

All timestamps are UTC in ISO 8601 format:

Timestamp Format
"2025-01-15T12:30:45.000Z"

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

Get All Items on Sale

GET /Items/GetAllItemsOnSale

Returns all items on sale, updated about every 5 minutes. Use the WebSocket for live changes.

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": 0.6416051387786865,
          "wearPercent": "35%",
          "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/...",
      "tradeUrl": "https://steamcommunity.com/tradeoffer/new/?partner=1551362882&token=czMFCtWq"
    }
  ],
  "lastUpdate": "2026-10-07T10:05:00.000Z"
}
FieldTypeDescription
totalItemsintegerTotal number of items
totalValueintegerTotal value of all items (USD cents)
lastUpdatestringTime this list was built (ISO 8601 UTC)
itemIdintegerItem ID (used for buy endpoint). Unique per item; use it as the item key
assetIdstringSteam Asset ID. Can repeat across items in rare cases
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, wearPercent, rotation?, offsetX?, offsetY?). wear is the raw float32 value returned by Steam inspect (range 0..1, where 0 = unscratched and 1 = fully scratched; null when inspect data is unavailable). wearPercent is a human-readable string for the remaining condition = floor((1 - wear) * 100) + "%": "100%" when wear=0 (pristine), "0%" when wear=1 (fully scratched); null when wear is null. Use whichever field fits your use case. 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
tradeUrlstring|nullTrade offer URL of the account holding this item (null if not enabled). Contact admin to enable

Search Items

POST /Items/SearchItems

Search items by name or item ID. Returns the latest availability and pricing for matching items.

Request Body

Provide exactly one of market_hash_name or itemId.

JSON - search by name
{
  "market_hash_name": [
    "AK-47 | Panthera onca (Minimal Wear)",
    "★ StatTrak™ Paracord Knife | Case Hardened (Well-Worn)"
  ]
}
JSON - search by item ID
{
  "itemId": [12345, 67890]
}
FieldTypeRequiredDescription
market_hash_namestring[]NoArray of market hash names to search (max 10)
itemIdinteger[]NoArray of item IDs to search (max 500, positive integers). More than 500 IDs is rejected

Response

Same format as Get All Items - returns only matching items.

Error Responses

HTTPError CodeDescription
400invalid_requestMissing, invalid, or conflicting search fields, or more than 500 item IDs

Get Balance

GET /Wallet/GetBalance

Returns merchant balance, credit limit, and total available.

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
fromstringNoCreated at or after this time (ISO 8601 UTC)
tostringNoCreated at or before this time (ISO 8601 UTC)
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
CRYPTO DEPOSITCrypto deposit top-up
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)",
      "phase": null
    }
  ],
  "total": 100,
  "limit": 50,
  "page": 1
}
FieldTypeDescription
idintegerTransaction ID
typestringTransaction type (see table above)
amountintegerAmount changed (USD cents, negative = deduct)
balanceBeforeintegerBalance before transaction (USD cents)
balanceAfterintegerBalance after transaction (USD cents)
tradeIdstringRelated trade ID (null if not trade-related)
notestringNote (null if none)
createdAtstringTransaction time (ISO 8601 UTC)
market_hash_namestringRelated item name (null if not trade-related)
phasestring|nullDoppler phase of the related item (null if not Doppler or not trade-related)
totalintegerTotal matching transactions (for pagination)
limitintegerResults per page
pageintegerCurrent page number

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": "★ Karambit | Doppler (Factory New)",
  "phase": "Phase 4",
  "float": 0.031245,
  "price": 1500,
  "externalId": "order_abc123"
}
FieldTypeDescription
tradeIdstringTrade ID - use this to match with webhooks
tradeUrlstringRecipient's Steam trade URL
assetIdstringSteam Asset ID of the purchased item
market_hash_namestringFull CS2 market hash name
phasestring|nullDoppler phase (null if not Doppler)
floatnumber|nullFloat value (null if the item has no float)
priceintegerPrice paid (USD cents)
externalIdstringYour order ID (null if not provided)
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 currently unavailable
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 (10 times in 24h). Includes expiresAt in message
403too_many_active_tradesTrade URL owner reached max 30 active offers (PENDING/SENT/CREATING), or max 5 active offers from the Steam account holding this item. For the 30 limit, wait for existing offers to finish; for the 5 limit, you can buy an item held by another Steam account

Buy Item by Name

POST /Items/BuyByName

Purchase an item by name. By default, picks the cheapest matching item. Optionally pass itemId and assetId to target a specific item with maxPrice protection.

Request Body

JSON
{
  "market_hash_name": "★ Karambit | Doppler (Factory New)",
  "phase": "Phase 4",
  "tradeUrl": "https://steamcommunity.com/tradeoffer/new/?partner=123&token=abc",
  "maxPrice": 1500,
  "externalId": "order_abc123",
  "tradeDurationSeconds": 1800,
  "itemId": 123,
  "assetId": "12345678901"
}
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). 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)
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_unavailableItem is currently unavailable
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 (10 times in 24h). Includes expiresAt in message
403too_many_active_tradesTrade URL owner reached max 30 active offers (PENDING/SENT/CREATING), or max 5 active offers from the Steam account holding this item. For the 30 limit, wait for existing offers to finish; for the 5 limit, you can buy an item held by another Steam account

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 14 days (no limit)
fromstringNoCreated at or after this time (ISO 8601 UTC). Cannot be used with isActive
tostringNoCreated at or before this time (ISO 8601 UTC). Cannot be used with isActive
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,
      "itemId": 123,
      "assetId": "12345678901",
      "offerId": "5678901234",
      "tradeUrl": "https://steamcommunity.com/tradeoffer/new/?partner=123&token=abc",
      "market_hash_name": "★ Karambit | Doppler (Factory New)",
      "phase": "Phase 4",
      "float": 0.031245,
      "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",
      "rollbackAt": null,
      "stage": 2
    }
  ],
  "total": 150,
  "limit": 50,
  "page": 1
}
FieldTypeDescription
tradeIdstringTrade ID (from buy endpoint)
externalIdstringYour order ID sent during purchase
statusstringCurrent trade status
priceintegerPrice (USD cents)
itemIdintegerItem ID
assetIdstringSteam Asset ID
offerIdstringSteam Trade Offer ID (null if not created yet)
tradeUrlstringRecipient's Steam trade URL
market_hash_namestringCS2 item name
phasestring|nullDoppler phase (null if not Doppler)
floatnumber|nullFloat value (null if unavailable)
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)
rollbackAtstringRefund time when a completed trade was reversed (ISO 8601 UTC, null if not reversed)
stageinteger1 = trade can still be reversed; 2 = trade can't be reversed
totalintegerTotal matching trades (for pagination)
limitintegerResults per page
pageintegerCurrent page number

Trade Statuses

These values appear in trade_updated payloads and GET /Trades/History responses.

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 on Steam by the seller or the buyer
REJECTEDTrade rejected before request received
Refund. If a completed trade is reversed by Steam, the merchant is refunded 100% 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
CANCELLED505Items no longer validTrade offer was sent but the item was sold elsewhere before the buyer accepted
CANCELLED506Trade counteredBuyer countered the trade offer
FAILED500(raw error message from Steam)General Steam error
REVERSED512Seller reversed trade on SteamSeller rolled back the completed trade on Steam
REVERSED512Buyer reversed trade on SteamBuyer rolled back the completed trade on Steam

WebSocket

Connect to wss://ws.buycs2.com for live trade and catalog events. Send these headers on the connection request. Each merchant can hold 3 connections.

HeaderValue
x-api-keyYour API key
x-merchant-idYour merchant ID

First frame: connected

Frame
{
  "type": "connected",
  "merchantId": "merchant_id"
}

trade_updated

Frame
{
  "type": "trade_updated",
  "tradeId": "abc-123-def",
  "status": "COMPLETED",
  "externalId": "order_abc123",
  "offerId": "5678901234",
  "failReason": null,
  "errorCode": null
}
FieldTypeDescription
typestringtrade_updated
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.

item_removed

These items are no longer for sale.

Frame
{ "type": "item_removed", "itemIds": [123, 456] }
FieldTypeDescription
typestringitem_removed
itemIdsinteger[]Item IDs

item_upserted

Same item fields as Get All Items.

Frame
{
  "type": "item_upserted",
  "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": [],
      "keyChains": [],
      "steamId": "76561198012345678",
      "inspectInGame": "steam://rungame/730/...",
      "tradeUrl": "https://steamcommunity.com/tradeoffer/new/?partner=1551362882&token=czMFCtWq"
    }
  ]
}

price_changed

Frame
{
  "type": "price_changed",
  "items": [{ "itemId": 123, "price": 1300 }]
}
FieldTypeDescription
typestringprice_changed
itemIdintegerItem ID
priceintegerNew price (USD cents)

Webhooks

A POST request is sent to your webhook URL when a trade status changes, when an item is no longer for sale, or when you should reload the sale list.

Webhook Authentication

HeaderValue
x-signatureHMAC-SHA256 of JSON body using API Key as secret
x-webhook-typetrade_updated, item_removed, or refresh_catalog
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

Same fields as the trade_updated WebSocket event, without type.

Payload
{
  "tradeId": "abc-123-def",
  "status": "COMPLETED",
  "externalId": "order_abc123",
  "offerId": "5678901234",
  "failReason": null,
  "errorCode": null
}

Webhook: item_removed

Same meaning as the item_removed WebSocket event. The body is one itemId.

Payload
{ "itemId": 123 }

Webhook: refresh_catalog

Sent once after every inventory sync (about every 5 minutes), when a new GetAllItemsOnSale list is ready. Call GetAllItemsOnSale and replace your local catalog.

Payload
{ "type": "refresh_catalog" }

Circuit Breaker

If your webhook endpoint keeps failing, delivery of all webhooks pauses. The first pause is 15 minutes. Each following pause lasts twice as long, up to 4 hours.

To resume, go to Merchant Portal → Settings and click Test Webhook. Your server must accept the test event and return HTTP 2xx. Updating the webhook URL also resumes delivery.

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.

Changelog

2026-10-04

  • Added refresh_catalog. Call GetAllItemsOnSale and replace your local catalog.
  • Added a WebSocket at wss://ws.buycs2.com for trade_updated, item_removed, item_upserted, and price_changed. Each merchant can hold 3 connections.

2026-09-18

  • GET /Trades/History and GET /Wallet/BalanceHistory accept optional from and to (ISO 8601 UTC) to filter createdAt.

2026-09-12

  • REVERSED failReason now identifies who rolled back the trade: Seller reversed trade on Steam or Buyer reversed trade on Steam.

2026-08-20

  • GET /Trades/History now includes stage: 1 = trade can still be reversed, 2 = trade can't be reversed.
  • If a completed trade is reversed by Steam (REVERSED), the merchant is refunded 100% of the trade value.