Partner with Skins.Cash
Five ways to work with the world's most trusted CS2 skin marketplace — from media deals and APIs to wholesale trading and tournament keys.
Skin Trading API
Do you have a huge amount of skins and are looking for a fast and simple way to trade them? Skins.Cash provides APIs to make this process as easy as pie.
Step 1
Sign in through Steam, confirm your email, and save a trade URL on Skins.Cash. Offers are sent to that trade link.
Step 2
On this page, click Generate credentials. The x-api-key is shown once — save it. Afterwards only a hint is available. Regenerating replaces the previous key immediately.
Step 3
Use the API to sell skins or check prices. Payout is always your Skins.Cash balance in EUR. CS2 proceeds stay in trade protection until settlement_time; Dota 2, TF2 and Rust are credited when the offer is accepted. Instant payout is not available.
If you have any questions, please feel free to contact us at wholesale@skins.cash.
Authorization
Every request except GET /docs sends one header over HTTPS:
x-api-key — your API key. Keys look like sc_live_ followed by 64 hexadecimal characters. There is no request signature and no salt.
Generate the key on this page. A confirmed email is required. The full key is returned once, in the response of that generation. skins.cash stores only a hash and can show the hint (prefix and last characters) afterwards. Regenerating replaces the previous key immediately. One key belongs to one account and one Steam inventory. Do not put the key in URLs, client-side code, or a public repository. If it leaks, regenerate it.
curl --location --request GET 'https://skins.cash/api/trading/v3/user' \ --header 'x-api-key: sc_live_3f9c1e0b7a6d4c2e8f1a0b9c8d7e6f5a4b3c2d1e0f9a8b7c6d5e4f3a2b1c0d9e'
A missing or unknown key, including a key that was just regenerated, answers HTTP 401 with error_code 100.
Selling rules
Payout is always the skins.cash balance of the account that owns the key, in EUR. Instant payout is not available through the API.
CS2 — after you accept the offer in Steam the trade becomes status 32 (Protected) with a settlement_time. The amount is shown as protected_balance and may be spent on the skins.cash Marketplace. It is credited to balance after settlement_time.
Dota 2, TF2, Rust — credited once the offer is accepted.
One trade per request. CS2 items cannot be mixed with other games (217). One open trade per account at a time (207) — wait until the previous offer is accepted or declined. The offer is sent to the trade link saved in the account. To cancel, decline the offer in Steam. There are no webhooks: poll GET /trade/{hash}.
POST /trade requires a client request_id (UUID). Repeating a request with the same id returns the trade already created for it and does not send another offer. A request rejected before a trade was created can be retried with the same id. After a trade with that id failed (for example status 6), use a new request_id.
GET /inventory/{app_id} returns, per item, the exact EUR amount it adds to a trade (account bonus and Doppler phase included). POST /trade sells that listing and does not reload it from Steam. The listing is kept for 35 minutes. After that a trade answers 209 until the inventory is fetched again. The same request_id can be reused. A trade sum is the sum of those listed prices (within cent rounding), as long as prices did not change in between.
GET /prices/{app_id} is the game price list before the account bonus. The exact amount for a specific item is only in the inventory response.
Per API key: 60 requests per minute for all endpoints except trade status, plus 60 requests per minute for GET /trade/{hash}. The Steam inventory is reloaded at most once per 60 seconds per game; inside that window the cached inventory is returned. Over the limit the API answers HTTP 429, error_code 222, and a Retry-After header (seconds).
Basic response
Every Trading API response uses the same envelope:
success — true when the request was processed, false on failure.error_code — 0 on success, otherwise a code from the Error codes section.data — the payload, or null on failure. For error_code 202, data.errors is a map of field names to messages.
Endpoints
Base URL: https://skins.cash/api/trading/v3
app_id is the Steam game id: 730 CS2, 570 Dota 2, 440 Team Fortress 2, 252490 Rust.
4.1. GET /docs
4.2. GET /user
4.3. GET /prices/{app_id}
4.4. GET /inventory/{app_id}
4.5. POST /trade
4.6. GET /trade/{hash}
{
"success": true,
"error_code": 0,
"data": {
"balance": 152.37,
"protected_balance": 48.10,
"currency": "EUR",
"bonus": 5
}
}data.balance — spendable balance, EUR.data.protected_balance — CS2 value still in Steam Trade Protection, EUR.data.currency — always EUR.data.bonus — active account bonus in percent. 5 means +5%. It is not included in GET /prices. Inventory prices already include it.
{
"success": true,
"error_code": 0,
"data": [
{
"name": "AK-47 | Redline (Field-Tested)",
"price": 12.34,
"skin_count": 15,
"phases": []
},
{
"name": "★ Karambit | Doppler (Factory New)",
"price": 540.12,
"skin_count": 2,
"phases": [
{ "phase": "Ruby", "price": 1080.24 },
{ "phase": "Phase 2", "price": 610.33 }
]
}
]
}data[].name — market hash name.data[].price — EUR at zero bonus, floored to cents. For a Doppler phase, use phases.data[].skin_count — how many more items of this name can be sold. Names with nothing left are omitted. A trade over the cap is rejected with 218.data[].phases — Doppler phases that have their own price. Empty when the name has no phases. Phases without their own price sell at price.
The extra amount sometimes applied to the most expensive CS2 item depends on the inventory and is not in this list. GET /inventory/{app_id} quotes the exact amount per item.
Only items skins.cash currently buys: tradable, priced, and within the stock cap of their name. Selling an item that is not listed answers 214.
{
"success": true,
"error_code": 0,
"data": [
{
"asset_id": "38211234567",
"class_id": "310776560",
"instance_id": "302028390",
"context_id": "2",
"app_id": 730,
"name": "★ Karambit | Doppler (Factory New)",
"price": 812.55,
"phase": "Phase 2",
"is_protected_game": true
}
]
}data[].price — EUR this item adds to a trade, including bonus and Doppler phase.data[].phase — resolved Doppler phase, or null.data[].is_protected_game — true for CS2: the sale goes through Steam Trade Protection.data[].app_id — game id as an integer.data[].asset_id — the id you send in POST /trade.
Call GET /inventory/{app_id} first. The body is read for request_id and items only. A trade URL in the body is not used.
{
"request_id": "6f1c2b9e-1d2a-4c7e-9a51-2e0f4b1a9c33",
"items": [
{ "app_id": 730, "asset_id": "38211234567" },
{ "app_id": 730, "asset_id": "38211239999" }
]
}request_id — required UUID. Letter case does not matter.items — required, at least one item.items[].app_id — integer, one of 730, 570, 440, 252490.items[].asset_id — digits only, from the inventory response.
curl --location --request POST 'https://skins.cash/api/trading/v3/trade' \
--header 'x-api-key: sc_live_...' \
--header 'Content-Type: application/json' \
--data-raw '{
"request_id": "6f1c2b9e-1d2a-4c7e-9a51-2e0f4b1a9c33",
"items": [
{ "app_id": 730, "asset_id": "38211234567" }
]
}'{
"success": true,
"error_code": 0,
"data": {
"request_id": "6f1c2b9e-1d2a-4c7e-9a51-2e0f4b1a9c33",
"hash": "a8f3c91d2e",
"status": 2,
"status_description": "Sent",
"is_protected": false,
"sum": 812.96,
"user_receives": 812.96,
"currency": "EUR",
"steam_trade_id": "6543210987",
"settlement_time": null,
"created_at": "2026-10-27T10:20:11Z"
}
}data.hash — id used by GET /trade/{hash}. Compare it with the offer in Steam.data.sum — trade total, EUR.data.user_receives — EUR credited to the balance.data.settlement_time — UTC. For a protected trade the balance is credited after this time. null until it is set.data.is_protected — true while a CS2 trade is in protection.
{
"success": false,
"error_code": 202,
"data": {
"errors": {
"request_id": ["The request id field is required."],
"items.0.app_id": ["The selected items.0.app_id is invalid."]
}
}
}POST /trade. Unknown or someone else's hash answers 404, error_code 212.{
"success": true,
"error_code": 0,
"data": {
"request_id": "6f1c2b9e-1d2a-4c7e-9a51-2e0f4b1a9c33",
"hash": "a8f3c91d2e",
"status": 32,
"status_description": "Protected",
"is_protected": true,
"sum": 812.96,
"user_receives": 812.96,
"currency": "EUR",
"steam_trade_id": "6543210987",
"settlement_time": "2026-11-03T10:25:00Z",
"created_at": "2026-10-27T10:20:11Z"
}
}Error codes
These are error_code values on the response envelope. Trade lifecycle values are in the next section.
| Code | HTTP | Meaning |
|---|---|---|
| 0 | 200 | No error |
| 100 | 401 | API key (x-api-key) missing or unknown |
| 101 | 401 | Account of the API key not found |
| 200 | 400 | Unsupported app_id |
| 201 | 424 | Failed to load the inventory from Steam, and no cached copy |
| 202 | 400 | Required parameter missing or invalid (see data.errors) |
| 205 | 400 | Account banned, including sanctions |
| 207 | 400 | A trade is already open or being created for this account |
| 208 | 400 | Failed to send the Steam trade offer |
| 209 | 400 | Cached inventory expired. Call GET /inventory/{app_id}, then retry. The same request_id is fine |
| 212 | 404 | Trade not found |
| 214 | 400 | Item is not in the cached inventory, or it has no price |
| 216 | 424 | Failed to load prices |
| 217 | 400 | CS2 items cannot be mixed with items of other games in one trade |
| 218 | 400 | Prices changed or the stock limit was reached. Reload the inventory |
| 219 | 400 | The account has no confirmed email |
| 220 | 400 | The account has no usable saved trade link |
| 221 | 400 | Trade sum is below the minimum |
| 222 | 429 | Rate limit exceeded. See the Retry-After header |
| 223 | 400 | Trade rejected by another business rule, for example anti-fraud |
| 224 | 500 | Internal error |
Trade statuses
status_description is the human-readable status. Other codes are error states; use that description.
| Status | Name | When |
|---|---|---|
| -1 | Creation failed | The trade was not created |
| 0 | Pending | Created, the Steam offer is not sent yet |
| 2 | Sent | The offer was sent |
| 3 | Accepted | Accepted for Dota 2, TF2 or Rust. Credited to the balance |
| 6 | Cancelled | The offer was cancelled |
| 7 | Declined | The offer was declined |
| 12 | Reverted | The trade was reverted |
| 20 | Payment in progress | Payment is in progress |
| 21 | Payed | The trade has been paid |
| 32 | Protected | CS2 offer accepted. Funds stay protected until settlement_time |
| 35 | Spent on Marketplace | Protected funds were spent on the Marketplace |
| 90 | Error | General Steam error |
| 91 | VAC Ban Error | VAC ban |
| 92 | Trade URL Error | The saved trade URL does not match the Steam account |
| 93 | Hold Error | Steam account limitation |
| 94 | Item not found | Item was not found in the inventory |
| 95 | Service unavailable | Steam is unavailable |
| 96 | Close for trades | Account is not available for trading |
| 97 | Trade locked | Trading is locked on the account |

