Appearance
Trades API
Query your trade history with flexible filtering and pagination.
Base path: /api/trades
Get Trade History
GET /api/trades
Returns your trade history with optional filtering by date range, custom IDs, or trade IDs. Supports pagination.
Hold purchases
Purchases of trade-locked items made via the Holds API enter this history only once their withdraw trade offer has been created (the trade record carries the same custom_id). Before that, track them via GET /api/items/holds.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
start_date | string | No | ISO date string for start of range |
end_date | string | No | ISO date string for end of range |
trade_ids | string | No | Comma-separated list of trade IDs (UUIDs) |
steam_trade_ids | string | No | Comma-separated list of Steam full trade IDs |
steam_trade_offer_ids | string | No | Comma-separated list of Steam tradeoffer IDs |
custom_ids | string | No | Comma-separated list of custom IDs |
limit | number | No | Results per page. Default: 50, max: 2000 |
offset | number | No | Number of results to skip. Default: 0 |
bash
curl -X GET "https://market.vpsbot.io/api/trades?limit=10&offset=0&start_date=2024-01-01T00:00:00Z" \
-H "x-api-key: YOUR_API_KEY"js
const { data } = await client.get('/trades', {
params: {
limit: 10,
offset: 0,
start_date: '2024-01-01T00:00:00Z'
}
});Example Response
json
{
"success": true,
"data": {
"trades": [
{
"trade_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"custom_id": "order-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-01-22T10:05:00Z"
},
"selected_bot": { // can be empty ("" fields) for some fulfilment paths
"steamid64": "76561198000000001",
"avatar": "https://avatars.steamstatic.com/...",
"profile_name": "VPSBot #5"
},
"status": "accepted",
"created_at": "2026-01-15T10:00:00Z",
"updated_at": "2026-01-15T10:05:00Z",
"error": null
}
],
"total": 42,
"limit": 50,
"offset": 0
}
}Cancel a Purchase
POST /api/trades/cancel
Cancel a purchase that has not been delivered yet and get a 100% refund to your balance.
Cancellation is available only for supported delayed-delivery purchases (instant_delivery: false items) and only before the seller sends the Steam trade offer. Once the offer is on its way, the purchase can no longer be cancelled. Every other purchase is refused with HTTP 409 and code: "not_cancellable".
A cancelled purchase ends with status: declined, summary: DeclinedByBuyer and error: "cancelled by buyer" in GET /api/trades. The usual realtime trade update is also sent.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
trade_id | string | One of | Trade ID (UUID) returned by the purchase |
custom_id | string | One of | Your custom ID of the purchase. If several open purchases share it, pass trade_id instead |
Leading and trailing whitespace is trimmed from both values, and a value that is empty after trimming counts as not sent. If both are sent, trade_id wins.
bash
curl -X POST "https://market.vpsbot.io/api/trades/cancel" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"trade_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"}'js
const { data } = await client.post('/trades/cancel', {
trade_id: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'
}).catch((err) => {
// Refusals answer 4xx / 5xx: axios throws, the envelope is in err.response
if (err.response) return err.response;
throw err;
});
if (!data.success) {
console.log(`Cancel refused: ${data.code} (${data.error})`);
}Example Response
json
{
"success": true,
"data": {
"trade_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"custom_id": "order-001",
"status": "declined",
"refunded": true,
"amount": 15200
}
}| Field | Type | Description |
|---|---|---|
trade_id | string | Trade ID |
custom_id | string | null | Your custom ID |
status | string | declined once the refund is applied. In rare cases it is still processing: the cancel went through and the refund lands within seconds |
refunded | boolean | true when the refund is already on your balance |
amount | number | Refunded amount (same unit as purchase prices) |
Errors
Unlike the purchase endpoints, a refused cancel keeps its HTTP status (404 / 409). The body is the usual envelope with the top-level code, plus meta.code — the earlier code of this endpoint, kept for backward compatibility. Branch on code:
json
{
"success": false,
"code": "delivery_in_progress",
"error": "The item is already being delivered",
"meta": { "code": "too_late" }
}| HTTP | code | error | meta.code | Meaning |
|---|---|---|---|---|
| 400 | validation_failed | Validation error | — | Neither trade_id nor custom_id sent (an empty value counts as not sent), or trade_id is not a UUID. data lists the invalid fields (see below) |
| 404 | purchase_not_found | Purchase not found | not_found | No purchase of yours matches |
| 409 | not_cancellable | The purchase cannot be cancelled | not_cancellable | This purchase type cannot be cancelled |
| 409 | delivery_in_progress | The item is already being delivered | too_late | The seller is already sending the item, or it was delivered |
| 409 | purchase_closed | The purchase is already closed | already_closed | The purchase is already finished (completed, declined, cancelled…) |
| 409 | custom_id_ambiguous | Several open purchases share this custom_id; pass trade_id | ambiguous | Several open purchases share this custom_id. Pass trade_id |
| 409 | seller_refused_cancel | The seller refused the cancellation | cancel_refused | The seller refused the cancellation |
| 500 | internal_error | Internal server error | not_ready | The purchase is still being placed. Retry in a few seconds |
| 500 | internal_error | Internal server error | provider_unavailable | The cancel could not be confirmed. Retry shortly |
| 500 | internal_error | Internal server error | — | The server failed to process the request. Retry shortly |
A 400 lists the invalid fields in data:
| Case | data |
|---|---|
| Neither field sent, or both empty | trade_id and custom_id, both with code: "required_without" |
trade_id is not a UUID after trimming | trade_id with code: "uuid" |
json
{
"success": false,
"code": "validation_failed",
"error": "Validation error",
"data": [
{ "field": "trade_id", "code": "uuid", "message": "must match format \"uuid\"" }
]
}Filtering Examples
By Date Range
bash
GET /api/trades?start_date=2026-01-01T00:00:00Z&end_date=2026-01-31T23:59:59ZBy Custom IDs
bash
GET /api/trades?custom_ids=order-001,order-002,order-003By Trade IDs
bash
GET /api/trades?trade_ids=a1b2c3d4-e5f6-7890-abcd-ef1234567890,b2c3d4e5-f6a7-8901-bcde-f12345678901By Steam Tradeoffer IDs
bash
GET /api/trades?steam_trade_offer_ids=5234567890,5234567891By Steam Full Trade IDs
bash
GET /api/trades?steam_trade_ids=4012345678_76561198012345678Pagination
bash
# Page 1 (first 50 results)
GET /api/trades?limit=50&offset=0
# Page 2 (next 50 results)
GET /api/trades?limit=50&offset=50Trade Object Schema (TradeData)
| Field | Type | Description |
|---|---|---|
trade_id | string | Internal trade identifier (UUID) |
custom_id | string | null | Your custom reference ID |
summary | string | Business-level status (see Trade Summary) |
status | string | IMT status (see Trade Statuses) |
created_at | string | null | ISO timestamp of trade creation |
updated_at | string | null | ISO timestamp of last update |
error | string | null | Error description if trade failed |
processed_trade | object | Processed trade details (see below) |
selected_bot | object | Bot that handled the trade |
selected_bot.steamid64 | string | Bot's Steam ID |
selected_bot.avatar | string | Bot's avatar URL |
selected_bot.profile_name | string | Bot's display name |
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 top-level trade_id) |
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 |
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 Item Schema (items_to_receive)
| Field | Type | Description |
|---|---|---|
appid | number | Game ID |
contextid | string | Steam context ID (always "2") |
assetid | string | Steam asset ID |
market_hash_name | string | Item market hash name |
amount | string | Always "1" |
Trade Statuses
| Status | Description |
|---|---|
processing | Trade is being processed (initial status) |
accepted | Trade accepted, funds frozen pending settlement (CS2 only) |
completed | Trade completed successfully |
declined | Trade declined (buyer-side failure, including a buyer cancel) |
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 |
Game-Specific Flow
The trade flow depends on the game's balance strategy:
| Game | Strategy | Flow |
|---|---|---|
| CS2 (730) | FREEZE_BALANCE | processing → accepted → completed (funds held in frozen balance until settlement) |
| Dota 2 (570) | BALANCE | processing → completed (funds credited directly) |
| TF2 (440) | BALANCE | processing → completed (funds credited directly) |
| Rust (252490) | BALANCE | processing → completed (funds credited directly) |
For BALANCE games (Dota 2, TF2, Rust), items are irreversible once accepted — the trade skips accepted and goes directly to completed with funds credited to the seller's main balance.
Trade Summary
The summary field is a business-level status derived from the Steam trade status and rollback side.
The side in each label names who performed the action. Which values you see depends on the purchase type: direct purchases (the bot gives the item — bot is the seller side) produce CanceledBySeller / DeclinedByBuyer; buy-order fills (the bot receives items — bot is the buyer side) produce CanceledByBuyer / DeclinedBySeller.
| Summary | Description |
|---|---|
InProgress | Trade is active, awaiting confirmation, or in escrow |
Accepted | Trade was accepted successfully |
CanceledByBuyer | The buyer side canceled the offer (buy-order fills) |
CanceledBySeller | The seller side canceled the offer (direct purchases) |
DeclinedByBuyer | The buyer declined the incoming offer (direct purchases) |
DeclinedBySeller | The seller declined the incoming offer (buy-order fills) |
RolledBackByBuyer | The buyer reversed the trade before settlement — no refund (reversal-actor policy) |
RolledBackBySeller | The seller reversed the trade before settlement — buyer refunded, seller reversed |
Failed | Trade failed (invalid, expired, missing items, etc.) |
Reversal money policy: the actor of a reversal eats the loss. A buyer-initiated reversal is never refunded (the seller still settles); a seller-initiated reversal refunds the buyer in full and reverses the seller's frozen credit. Reversals after settlement never move money. See Supported Games & Trade Flows for the full per-game flows.
Error Field
When the trade status is declined or cancelled, the error field contains a free-form string describing the failure reason.