Appearance
Sell API
Sell your Steam items to VIP buyers through the matching engine. The sell flow is a two-step process: first match your items against active buy orders, then create a trade with the matched buyer. Use the tracking endpoints to monitor trade progress.
Base path: /api/sell
Match Items
POST /api/sell/match
Match your inventory items against active buy orders. Returns matched prices and the buyer ID. Use this before creating a trade to verify which items have demand and at what price.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
game_id | number | Yes | Game ID (730, 570, 252490, 440) |
items | array | Yes | Array of items to match (min 1) |
items[].market_hash_name | string | Yes | Steam market hash name |
items[].amount | number | No | Quantity to match. Default: 1 |
bash
curl -X POST "https://market.vpsbot.io/api/sell/match" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"game_id": 730,
"items": [
{ "market_hash_name": "AK-47 | Redline (Field-Tested)", "amount": 2 },
{ "market_hash_name": "AWP | Asiimov (Field-Tested)", "amount": 1 }
]
}'js
const { data } = await client.post('/sell/match', {
game_id: 730,
items: [
{ market_hash_name: 'AK-47 | Redline (Field-Tested)', amount: 2 },
{ market_hash_name: 'AWP | Asiimov (Field-Tested)', amount: 1 }
]
});Example Response
json
{
"success": true,
"data": {
"buyer_id": "3f6c1a2e-8b4d-4e7a-9c15-2d0b7e6f4a91",
"buyability": 250000,
"items": [
{
"market_hash_name": "AK-47 | Redline (Field-Tested)",
"price": 14500,
"amount": 2
},
{
"market_hash_name": "AWP | Asiimov (Field-Tested)",
"price": 32000,
"amount": 1
}
]
}
}TIP
Items with price: 0 have no matching buy order. Only items with a non-zero price can be sold.
Response Fields
| Field | Type | Description |
|---|---|---|
success | boolean | See Response Format |
data.buyer_id | string | Matched VIP buyer's identifier (UUID). Pass this to /sell/trades/create. Empty when nothing matched |
data.buyability | number | Maximum total we will buy in this match (1$ = 1000). Your basket must stay within it. 0 when nothing matched |
data.items | array | Items with matched prices |
data.items[].market_hash_name | string | Steam market hash name |
data.items[].price | number | Matched buy order price (1$ = 1000) (0 = no match) |
data.items[].amount | number | Matched quantity |
TIP
buyability is a cap on the whole match, not a per-item limit. Sum price × amount across the returned items and keep the total within it — quantities are not trimmed to the cap for you. A basket that goes over still matches here, but /sell/trades/create refuses it with code: "buyability_exceeded". Drop or split items until the total fits.
Errors
Finding no match is not an error: the response is a success with an empty buyer_id and items.
| HTTP | code | error | Meaning |
|---|---|---|---|
| 400 | validation_failed | Validation error | The request body is invalid. data lists the invalid fields |
data[].field | data[].code | Meaning |
|---|---|---|
game_id | required | game_id is missing |
game_id | oneof | game_id is not one of 730, 570, 440, 252490 |
items | required | items is missing |
items | min | items is empty |
items[0].market_hash_name | required | The item has no market_hash_name. The index is the item's position in items |
Create Trade
POST /api/sell/trades/create
Create a sell trade with a matched buyer. This reserves the purchase, assigns a bot, and sends a Steam trade offer to collect your items.
WARNING
You must call /api/sell/match first to obtain the buyer_id and verify item prices. The buyer_id and item prices must match current buy orders, otherwise the trade is refused (see Refused Trade).
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
custom_id | string | Yes | Your unique trade identifier (used for idempotency) |
buyer_id | string | Yes | Buyer ID (UUID) returned from /api/sell/match |
tradelink | string | Yes | Your Steam trade offer URL |
game_id | number | Yes | Game ID (730, 570, 252490, 440) |
items | array | Yes | Array of items to sell (min 1) |
items[].assetid | string | Yes | Steam asset ID of the item |
items[].market_hash_name | string | Yes | Steam market hash name |
items[].price | number | Yes | Sale price (1$ = 1000) (must not exceed buy order price) |
items[].contextid | string | No | Steam context ID. Default: "2" |
message | string | No | Optional message to include in the trade offer |
bash
curl -X POST "https://market.vpsbot.io/api/sell/trades/create" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"custom_id": "sell-001",
"buyer_id": "3f6c1a2e-8b4d-4e7a-9c15-2d0b7e6f4a91",
"tradelink": "https://steamcommunity.com/tradeoffer/new/?partner=XXXXX&token=YYYYY",
"game_id": 730,
"items": [
{
"assetid": "38291847562",
"market_hash_name": "AK-47 | Redline (Field-Tested)",
"price": 14500
},
{
"assetid": "49382716453",
"market_hash_name": "AK-47 | Redline (Field-Tested)",
"price": 14500
},
{
"assetid": "27461938274",
"market_hash_name": "AWP | Asiimov (Field-Tested)",
"price": 32000
}
]
}'js
const { data } = await client.post('/sell/trades/create', {
custom_id: 'sell-001',
buyer_id: '3f6c1a2e-8b4d-4e7a-9c15-2d0b7e6f4a91',
tradelink: 'https://steamcommunity.com/tradeoffer/new/?partner=XXXXX&token=YYYYY',
game_id: 730,
items: [
{ assetid: '38291847562', market_hash_name: 'AK-47 | Redline (Field-Tested)', price: 14500 },
{ assetid: '49382716453', market_hash_name: 'AK-47 | Redline (Field-Tested)', price: 14500 },
{ assetid: '27461938274', market_hash_name: 'AWP | Asiimov (Field-Tested)', price: 32000 }
]
});
if (!data.success) {
// A refused trade answers HTTP 200: branch on data.code
switch (data.code) {
case 'buyer_changed':
case 'no_buy_order':
case 'price_changed':
// call /sell/match again and rebuild the trade from its result
break;
case 'buyability_exceeded':
// drop or split items until the total fits buyability
break;
case 'duplicate_custom_id':
// the trade already exists: look it up by custom_id
break;
case 'invalid_trade_url':
// check the tradelink: regenerate it in Steam if it is out of date
break;
default:
throw new Error(`Trade refused: ${data.code} (${data.error})`);
}
}Success Response (201)
json
{
"success": true,
"data": {
"trade_id": "a1b2c3d4-...",
"custom_id": "sell-001",
"summary": {
"items_count": 3,
"total_value": 61000
},
"processed_trade": {
"id": "a1b2c3d4-...",
"status": "processing"
},
"selected_bot": {
"steamid64": "76561198999999999",
"avatar": "https://avatars.steamstatic.com/...",
"profile_name": "VPSBot #12"
},
"status": "processing"
}
}Response Fields
| Field | Type | Description |
|---|---|---|
success | boolean | See Response Format |
data.trade_id | string | Market trade UUID |
data.custom_id | string | Your custom ID echoed back |
data.summary | object | Trade summary |
data.summary.items_count | number | Number of items in the trade |
data.summary.total_value | number | Total trade value (1$ = 1000) |
data.processed_trade | object | Processed trade details |
data.processed_trade.id | string | Internal trade UUID |
data.processed_trade.status | string | Initial trade status (processing) |
data.selected_bot | object | The bot that will send the trade offer |
data.selected_bot.steamid64 | string | Bot's Steam ID |
data.selected_bot.avatar | string | Bot's avatar URL |
data.selected_bot.profile_name | string | Bot's display name |
data.status | string | Trade status (processing) |
Refused Trade
If the trade cannot be created, it is refused: no trade is created and no trade offer is sent. The response is HTTP 200 with success: false, a machine-readable code and a human-readable error:
json
{
"success": false,
"code": "buyer_changed",
"error": "The buyer for these items has changed; call /match again"
}code | error | Meaning |
|---|---|---|
duplicate_custom_id | A trade with this custom_id already exists | A trade with this custom_id already exists. Look it up with GET /api/sell/trades/by-custom-id/{custom_id}, or send a new custom_id |
buyer_changed | The buyer for these items has changed; call /match again | The buyer_id you sent can no longer take these items. Call /match again and create the trade with the new buyer_id |
no_buy_order | No buy order found for item: <name> | An item no longer has a matching buy order. Call /match again |
price_changed | Item <name> price <price> exceeds buy order price <buy order price> | An item's price is above its current buy order price. Call /match again for the current prices |
buyability_exceeded | Trade total exceeds the buyability returned by /match | The trade total is above the buyability returned by /match. Drop or split items until the total fits |
invalid_trade_url | The trade URL cannot receive trade offers | Your tradelink cannot receive trade offers: it is malformed or out of date (regenerate it in Steam), or the account cannot trade right now. Check the tradelink |
trade_partner_banned | Your Steam account has a trade ban and cannot receive trade offers | The Steam account behind your tradelink has a trade ban and cannot receive trade offers. Retrying fails the same way until the ban is lifted |
trade_banned | The bot for this trade has a Steam trade ban | No bot can take this trade right now because of a Steam trade ban. Retry later |
Branch on code, not on the error text.
Invalid Request
An invalid request body answers HTTP 400 with code: "validation_failed", and data lists the invalid fields:
json
{
"success": false,
"code": "validation_failed",
"error": "Validation error",
"data": [
{ "field": "tradelink", "code": "required", "message": "tradelink is a required field" },
{ "field": "items[0].price", "code": "gt", "message": "price must be greater than 0" }
]
}data[].field | data[].code | Meaning |
|---|---|---|
custom_id | required | custom_id is missing |
buyer_id | required | buyer_id is missing |
buyer_id | uuid | buyer_id is not a UUID |
tradelink | required | tradelink is missing |
game_id | required | game_id is missing |
game_id | oneof | game_id is not one of 730, 570, 440, 252490 |
items | required | items is missing |
items | min | items is empty |
items[0].market_hash_name | required | The item has no market_hash_name. The index is the item's position in items |
items[0].assetid | required | The item has no assetid |
items[0].price | required | The item has no price, or it is 0 |
items[0].price | gt | The item's price is below 0 |
List Trades
GET /api/sell/trades
Retrieve a paginated list of your sell trades with optional filtering and sorting.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
game_id | number | No | Filter by game ID (730, 570, 252490, 440) |
status | string | No | Filter by trade status |
date_from | string | No | Filter trades created after this date (ISO 8601) |
date_to | string | No | Filter trades created before this date (ISO 8601) |
price_min | number | No | Minimum trade value (1$ = 1000) |
price_max | number | No | Maximum trade value (1$ = 1000) |
custom_id | string | No | Filter by your custom trade ID (ILIKE search) |
custom_ids | string | No | Comma-separated list of custom IDs for batch lookup |
trade_ids | string | No | Comma-separated list of market trade UUIDs for batch lookup |
steam_trade_offer_ids | string | No | Comma-separated list of Steam tradeoffer IDs for batch lookup |
steam_trade_ids | string | No | Comma-separated list of Steam full trade IDs for batch lookup |
sort_by | string | No | Sort field (e.g. created_at, purchase_price) |
sort_direction | string | No | Sort direction: asc or desc |
limit | number | No | Number of results per page. Default: 20 |
offset | number | No | Number of results to skip. Default: 0 |
bash
curl "https://market.vpsbot.io/api/sell/trades?game_id=730&status=accepted&limit=10&offset=0" \
-H "x-api-key: YOUR_API_KEY"js
const { data } = await client.get('/sell/trades', {
params: { game_id: 730, status: 'accepted', limit: 10, offset: 0 }
});Example Response
json
{
"success": true,
"data": {
"trades": [
{
"trade_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"custom_id": "sell-001",
"summary": "Accepted",
"processed_trade": {
"market": "market-vpsbot",
"trade_id": "4012345678_76561198012345678",
"tradeoffer_id": "5234567890",
"request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"partner_id": "https://steamcommunity.com/tradeoffer/new/?partner=XXXXX&token=YYYYY",
"items_to_give": [],
"items_to_receive": [
{
"appid": 730,
"contextid": "2",
"assetid": "30000000001",
"market_hash_name": "AK-47 | Redline (Field-Tested)",
"amount": "1"
}
],
"status": 3,
"game_id": 730,
"settlement": "2026-03-20T10:05:00Z"
},
"selected_bot": {
"steamid64": "76561198000000001",
"avatar": "https://avatars.steamstatic.com/...",
"profile_name": "VPSBot #12"
},
"status": "accepted",
"created_at": "2026-03-13T10:00:00Z",
"updated_at": "2026-03-13T10:05:00Z",
"error": null
}
],
"total": 1,
"limit": 10,
"offset": 0
}
}Response Fields
| Field | Type | Description |
|---|---|---|
data.trades | array | List of trades |
data.trades[].trade_id | string | Market trade UUID |
data.trades[].custom_id | string | Your custom trade ID |
data.trades[].summary | string | Business-level status (see Trade Summary) |
data.trades[].status | string | IMT status (see Trade Statuses) |
data.trades[].created_at | string | null | Trade creation timestamp (ISO 8601) |
data.trades[].updated_at | string | null | Last update timestamp (ISO 8601) |
data.trades[].error | string | null | Error message if trade failed |
data.trades[].processed_trade | object | Processed trade details (see below) |
data.trades[].selected_bot | object | Bot that handled the trade |
data.trades[].selected_bot.steamid64 | string | Bot's Steam ID |
data.trades[].selected_bot.avatar | string | Bot's avatar URL |
data.trades[].selected_bot.profile_name | string | Bot's display name |
data.total | number | Total number of matching trades |
data.limit | number | Page size |
data.offset | number | Current offset |
ProcessedTrade Object
| Field | Type | Description |
|---|---|---|
market | string | Always "market-vpsbot" |
trade_id | string | Steam full trade ID |
tradeoffer_id | string | Steam trade offer ID |
request_id | string | Market trade UUID (same as trade_id at top level) |
partner_id | string | Steam trade offer URL |
items_to_give | array | Items given (always empty for sell trades) |
items_to_receive | array | Items received in the trade |
items_to_receive[].appid | number | Game ID |
items_to_receive[].contextid | string | Steam context ID |
items_to_receive[].assetid | string | Steam asset ID |
items_to_receive[].market_hash_name | string | Item market hash name |
items_to_receive[].amount | string | Always "1" |
status | number | Numeric Steam trade status |
game_id | number | Game ID (730, 570, 252490, 440) |
settlement | string | null | ISO timestamp of when Steam settles the trade (CS2 only) |
Trade Dashboard
GET /api/sell/trades/dashboard
Get aggregated statistics for your sell trades.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
game_id | number | No | Filter stats by game ID |
bash
curl "https://market.vpsbot.io/api/sell/trades/dashboard?game_id=730" \
-H "x-api-key: YOUR_API_KEY"js
const { data } = await client.get('/sell/trades/dashboard', {
params: { game_id: 730 }
});Example Response
json
{
"success": true,
"data": {
"total_transactions": 150,
"total_volume": 6750000,
"total_revenue": 6750000,
"avg_transaction": 45000,
"success_rate": 80.0,
"accepted_count": 100,
"completed_count": 20,
"processing_count": 5,
"error_count": 3,
"cancelled_count": 7,
"declined_count": 5,
"rolled_back_by_user_count": 8,
"rolled_back_by_bot_count": 2
}
}Response Fields
| Field | Type | Description |
|---|---|---|
data.total_transactions | number | Total number of trades |
data.total_volume | number | Total volume of successful trades (1$ = 1000) |
data.total_revenue | number | Total revenue from successful trades (1$ = 1000) |
data.avg_transaction | number | Average transaction value (1$ = 1000) |
data.success_rate | number | Percentage of successful trades |
data.accepted_count | number | Accepted trades |
data.completed_count | number | Completed trades |
data.processing_count | number | Currently processing trades |
data.error_count | number | Failed trades |
data.cancelled_count | number | Cancelled trades (seller-side failure) |
data.declined_count | number | Declined trades (buyer-side failure) |
data.rolled_back_by_user_count | number | Trades rolled back by the user |
data.rolled_back_by_bot_count | number | Trades rolled back by the bot |
Errors
| HTTP | code | error | Meaning |
|---|---|---|---|
| 400 | validation_failed | Validation error | game_id is not an integer. data lists it with field: "game_id" and code: "type" |
Get Trade by Custom ID
GET /api/sell/trades/by-custom-id/{custom_id}
Look up a trade using your custom identifier.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
custom_id | string | Your custom trade ID |
bash
curl "https://market.vpsbot.io/api/sell/trades/by-custom-id/sell-001" \
-H "x-api-key: YOUR_API_KEY"js
try {
const { data } = await client.get('/sell/trades/by-custom-id/sell-001');
console.log(data.data.status);
} catch (err) {
// An unknown trade answers HTTP 404, which axios throws
if (err.response?.data?.code === 'not_found') {
console.log('No trade with this custom_id');
} else {
throw err;
}
}Example Response
Returns a single TradeData object. See List Trades for the full schema.
Errors
| HTTP | code | error | Meaning |
|---|---|---|---|
| 404 | not_found | Trade not found | No trade matches this custom_id |
Get Trade by Steam Tradeoffer ID
GET /api/sell/trades/by-steam-tradeoffer-id/{tradeoffer_id}
Look up a trade using the Steam trade offer ID.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
tradeoffer_id | string | Steam trade offer ID |
bash
curl "https://market.vpsbot.io/api/sell/trades/by-steam-tradeoffer-id/5234567890" \
-H "x-api-key: YOUR_API_KEY"js
const { data } = await client.get('/sell/trades/by-steam-tradeoffer-id/5234567890');Example Response
Returns a single TradeData object. See List Trades for the full schema.
Errors
| HTTP | code | error | Meaning |
|---|---|---|---|
| 404 | not_found | Trade not found | No trade matches this tradeoffer_id |
Get Trade by Steam Trade ID
GET /api/sell/trades/by-steam-trade-id/{trade_id}
Look up a trade using the Steam trade ID. Only available for trades with status accepted or rolled_back_by_user / rolled_back_by_bot.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
trade_id | string | Steam trade ID |
bash
curl "https://market.vpsbot.io/api/sell/trades/by-steam-trade-id/4012345678" \
-H "x-api-key: YOUR_API_KEY"js
const { data } = await client.get('/sell/trades/by-steam-trade-id/4012345678');Example Response
Returns a single TradeData object. See List Trades for the full schema.
Errors
| HTTP | code | error | Meaning |
|---|---|---|---|
| 404 | not_found | Trade not found | No trade matches this trade_id |
Get Trade by ID
GET /api/sell/trades/by-id/{id}
Look up a trade using its market trade UUID.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id | string | Market trade UUID |
bash
curl "https://market.vpsbot.io/api/sell/trades/by-id/a1b2c3d4-..." \
-H "x-api-key: YOUR_API_KEY"js
const { data } = await client.get('/sell/trades/by-id/a1b2c3d4-...');Example Response
Returns a single TradeData object. See List Trades for the full schema.
Errors
| HTTP | code | error | Meaning |
|---|---|---|---|
| 404 | not_found | Trade not found | No trade matches this id |
Error Responses
Errors use the common Response Format: check success, then branch on code. Each endpoint above lists its own codes.
An unknown path under /api/sell, or an existing path called with an unsupported HTTP method, answers HTTP 404 with code: "not_found" and error: "Not found".
Trade Statuses
Possible values for the status field on sell trades:
| Status | Description |
|---|---|
processing | Trade is being processed (initial status) |
accepted | Trade accepted by both parties |
completed | Trade completed successfully |
declined | Trade declined (buyer-side failure) |
cancelled | Trade cancelled (seller-side failure) |
rolled_back_by_user | Trade rolled back by the user |
rolled_back_by_bot | Trade rolled back by the bot |
Steam Trade Statuses
Possible values for the trade_status field (reflects the Steam trade offer state):
| Status | Description |
|---|---|
Invalid | Trade offer is invalid |
Active | Trade offer is active and awaiting action |
Accepted | Trade offer was accepted |
Countered | Trade offer was countered |
Expired | Trade offer expired |
Canceled | Trade offer was canceled by the sender |
Declined | Trade offer was declined by the recipient |
InvalidItems | Items in the trade are no longer valid |
NeedsConfirmation | Trade needs mobile confirmation |
CanceledBy2FA | Trade was canceled due to 2FA |
InEscrow | Trade is in escrow (trade hold) |
RolledBack | Trade was rolled back by Steam |
MissingItems | Items are missing from the trade |
Sell Flow Overview
- Match - Call
POST /api/sell/matchwith your items to get prices, abuyer_idandbuyability— the maximum total we will buy in this match - Review - Check which items have
price > 0(matched with a buy order), and that their total stays withinbuyability - Create Trade - Call
POST /api/sell/trades/createwith thebuyer_id, your items' asset IDs, and prices - Accept Trade - A bot will send you a Steam trade offer. Accept it to complete the sale
- Track Trade - Use the trade lookup endpoints to monitor trade status (e.g.
GET /api/sell/trades/by-custom-id/{custom_id}) - Settlement - Once the trade is confirmed on Steam, the sale settles and your payout is credited. Timing differs per game — see Supported Games & Trade Flows
Idempotency
The custom_id field acts as an idempotency key. If you retry with the same custom_id, you get code: "duplicate_custom_id" (HTTP 200) instead of a duplicate trade.