TradeDeskDownload OpenAPI JSON

CS2 skin cashout API

Price skins, send offers and withdraw settled funds from your Trade Desk account.

Planning an integration? Explore the cashout API workflow or apply for a merchant account.

Get started

In Account settings, generate your API key with a fresh authenticator code. Save it on your server as TRADE_DESK_KEY. Send one Authorization: Bearer header per request. Keep the key out of browser code, URLs and logs.

Use /api/test/v1 for integration tests and /api/v1 for live requests. The test environment uses synthetic inventories, trades, balances and payments. Its records expire after an hour or a server restart. Use the example Steam URL in the inventory endpoint below.

const base = "https://tradedesk.click/api/test/v1";
const response = await fetch(base + "/prices", {
  method: "POST",
  headers: { Authorization: "Bearer " + process.env.TRADE_DESK_KEY,
             "Content-Type": "application/json" },
  body: JSON.stringify({ search: "Redline", limit: 10 })
});
const result = await response.json();
if (!response.ok) throw new Error(result.error);
console.log(result.items);

Authentication & permissions

Each account can hold one active key. Copy it when you generate it. Replacing or revoking it ends access with that credential. Call the API from your server over HTTPS, send POST parameters in a JSON body and use the account key on both test and live paths. Browser sessions, cookies and query strings are rejected.

read
Look up your account’s prices, balance, trades and withdrawals.
trade
Load a customer inventory, prepare a quote and submit the accepted trade.
withdraw
Request a payment from your available balance. To use the address saved on your key, select its approved token and chain.
withdrawals:customer
Adds the option to supply a seller’s wallet address with each payment.

A valid key can exercise the synthetic flows in the sandbox. Live operations require the corresponding key permissions and account access. A sandbox trade or withdrawal ID belongs to the test environment.

Prices & pagination

POST /prices returns indicative cashout prices in USD cents at your account’s rate. Use game 730 for CS2 (the default) or 252490 for Rust. A price of 1000 means $10.00. A missing price is null. Check available even when the item has a price.

You can show your TradeDesk offer to a customer or apply your own margin, then use POST /inventory and POST /quotes to confirm what your account will receive for the selected assets.

To fetch the catalog, start with {"offset":0,"limit":100}. Send each returned nextOffset as the next request’s offset until nextOffset is null. Pages default to 50 items and allow up to 100. An optional search narrows the results by name or phase.

For exact matches, submit up to 100 names in items, with the finish phase when applicable. Include wear and variant in the name. Batch lookup and paginated search are separate request shapes.

{
  "items": [
    {
      "name": "AK-47 | Redline (Field-Tested)"
    }
  ]
}

Cache results per merchant and query. Send the returned version with the same request to check for changes. An unchanged result contains only version and unchanged: true. A full result can have stale: true while serving cached prices.

New in v1.1 · Direct customer payouts

Your storefront can pay a seller’s wallet directly from your merchant account’s available balance. Your server chooses when to request the payment and how much to send. Check GET /balance before submitting.

Once your account has API withdrawal permission, open Account → API access and select Allow payouts to customer wallets. Generate or replace your key with a fresh authenticator code, then install the new key on your server. A live key authorized only for its fixed wallet returns 403 if you supply a customer address.

Try this $25 USDC payment in the sandbox. For a live request, use /api/v1/withdrawals and the seller’s address on the selected network. You can supply each address directly without saving it in the portal.

POST /api/test/v1/withdrawals
Authorization: Bearer <your-api-key>
Content-Type: application/json
Idempotency-Key: seller-payout-1042

{
  "amountCents": 2500,
  "token": "USDC",
  "chain": "ETH",
  "address": "0x1111111111111111111111111111111111111111"
}

Save one Idempotency-Key per seller payment before sending it. If the response is lost, resend the original request with that key. A different recipient with the same key returns 409. Save the returned withdrawal ID and check GET /withdrawals/{id} until it reports paid or cancelled.

Endpoint reference

Paths below are relative to the test or live base. Successful requests return HTTP 200 with a JSON object. Keep Steam and asset identifiers as strings. For every {id} path, substitute the ID returned by the creation request.

POST /prices

Look up deposit prices. Search with offset and limit, or send up to 100 exact names and phases in items. Prices are indicative USD cents at the account’s rate. Reuse version with the same query to check for changes.

Permission read · 30 requests/min per account

Request body
{
  "search": "Redline",
  "limit": 10
}
Example response · 200 OK
{
  "game": "730",
  "currency": "USD",
  "stale": false,
  "version": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
  "items": [
    {
      "name": "AK-47 | Redline (Field-Tested)",
      "phase": null,
      "priceCents": 1000,
      "available": true
    }
  ],
  "total": 1,
  "nextOffset": null
}

Returns game, currency, stale, version, items, total, nextOffset

POST /inventory

Load an inventory. Supply the customer’s Steam trade URL. Item IDs belong to this inventory. Choose available items for a quote.

Permission trade · 4 requests/min per account

Request body
{
  "tradeUrl": "https://steamcommunity.com/tradeoffer/new/?partner=1&token=PREVIEW1",
  "game": "730"
}
Example response · 200 OK
{
  "inventoryId": "10000000-0000-4000-8000-000000000001",
  "steamId": "76561197960265729",
  "expiresAt": "2026-11-10T12:30:00.000Z",
  "currency": "USD",
  "items": [
    {
      "name": "AK-47 | Redline (Field-Tested)",
      "phase": null,
      "priceCents": 1000,
      "available": true,
      "id": "asset:730:1001",
      "assetId": "1001"
    }
  ]
}

Returns inventoryId, steamId, expiresAt, currency, items

POST /quotes

Review an offer. Use the inventoryId you received and select 1–50 different item IDs from that inventory. Review the quoted items and total with the seller before submission. Set autoCancelMinutes (5–120, default 120) for the Steam offer. The quote review deadline is expiresAt.

Permission trade · 12 requests/min per account

Request body
{
  "inventoryId": "10000000-0000-4000-8000-000000000001",
  "itemIds": [
    "asset:730:1001"
  ]
}
Example response · 200 OK
{
  "quoteId": "20000000-0000-4000-8000-000000000002",
  "expiresAt": "2026-11-10T12:30:00.000Z",
  "currency": "USD",
  "totalCents": 1000,
  "items": [
    {
      "name": "AK-47 | Redline (Field-Tested)",
      "phase": null,
      "priceCents": 1000,
      "available": true,
      "id": "asset:730:1001",
      "assetId": "1001"
    }
  ],
  "autoCancelMinutes": 120
}

Returns quoteId, expiresAt, currency, totalCents, items, autoCancelMinutes

POST /trades

Submit an offer. Send the accepted quote using one UUID Idempotency-Key. Save the resulting trade ID, even when status is processing. Repeat an uncertain request with the original key and quote.

Permission trade · 6 requests/min per account

Request body
{
  "quoteId": "20000000-0000-4000-8000-000000000002"
}
Example response · 200 OK
{
  "id": "40000000-0000-4000-8000-000000000004",
  "status": "awaiting_acceptance",
  "createdAt": "2026-11-10T12:00:00.000Z",
  "game": "730",
  "steamId": "76561197960265729",
  "currency": "USD",
  "totalCents": 1000,
  "legs": [
    {
      "status": "pending",
      "itemIds": [
        "asset:730:1001"
      ],
      "totalCents": 1000,
      "availableCreditCents": 0,
      "pendingCreditCents": 0,
      "steamOfferUrl": null
    }
  ]
}

Returns id, status, createdAt, game, steamId, currency, totalCents, legs

GET /trades/{id}

Read a trade. Returns the current state and each split offer. Each leg shows the actual Steam link when available and the pending and available credits for its items.

Permission read · 30 requests/min per account

Example response · 200 OK
{
  "id": "40000000-0000-4000-8000-000000000004",
  "status": "awaiting_acceptance",
  "createdAt": "2026-11-10T12:00:00.000Z",
  "game": "730",
  "steamId": "76561197960265729",
  "currency": "USD",
  "totalCents": 1000,
  "legs": [
    {
      "status": "pending",
      "itemIds": [
        "asset:730:1001"
      ],
      "totalCents": 1000,
      "availableCreditCents": 0,
      "pendingCreditCents": 0,
      "steamOfferUrl": null
    }
  ]
}

Returns id, status, createdAt, game, steamId, currency, totalCents, legs

GET /balance

Read your balance. Amounts are USD cents. availableCents is the balance eligible for withdrawal. Pending and reserved amounts are tracked separately.

Permission read · 30 requests/min per account

Example response · 200 OK
{
  "availableCents": 10000,
  "pendingCents": 0,
  "reservedCents": 0,
  "currency": "USD"
}

Returns availableCents, pendingCents, reservedCents, currency

POST /withdrawals

Request a withdrawal. Pay from your available balance. Omit address to use the key’s approved wallet for that token and chain. Include the customer’s address to pay them directly with withdrawals:customer permission. Withdrawal mode, limits and enabled currencies apply. Retry with the same Idempotency-Key and the same amount, token, chain and destination. Changing the recipient on a retry returns 409.

Permission withdraw · 6 requests/min per account

Request body
{
  "amountCents": 1000,
  "token": "USDC",
  "chain": "ETH"
}

For a customer wallet, see the direct payout request.

Example response · 200 OK
{
  "id": "50000000-0000-4000-8000-000000000005",
  "amountCents": 2500,
  "token": "USDC",
  "chain": "ETH",
  "status": "pending",
  "reserved": true,
  "createdAt": "2026-11-10T12:00:00.000Z",
  "txHash": null
}

Returns id, amountCents, token, chain, status, reserved, createdAt, txHash

GET /withdrawals/{id}

Read a withdrawal. Read an API withdrawal owned by your account. Pending or processing can include a delayed payment under review. A reserved payment can still settle.

Permission read · 30 requests/min per account

Example response · 200 OK
{
  "id": "50000000-0000-4000-8000-000000000005",
  "amountCents": 2500,
  "token": "USDC",
  "chain": "ETH",
  "status": "pending",
  "reserved": true,
  "createdAt": "2026-11-10T12:00:00.000Z",
  "txHash": null
}

Returns id, amountCents, token, chain, status, reserved, createdAt, txHash

Settlement & retries

  1. Load the inventory, select available item IDs and create a quote. The amount is fixed for that review until its expiresAt. Inventory snapshots can be lost on restart. Reload the inventory if a review cannot be created.
  2. Show the quote to the seller. Persist a UUID for this submission, then send POST /trades with that UUID as the Idempotency-Key. If the result is unclear, repeat the same quote and key. Save the returned ID to track the trade.
  3. If a connection drops during submission, the request can stay processing while Trade Desk checks its original reference. A retry returns that record. For a persistent unconfirmed offer, give the trade ID to the administrator for reconciliation.
  4. Each Steam offer appears in legs. On acceptance a leg can enter hold. An offer can be canceled or reversed, and a split trade can finish partially completed. Use the credited amounts for each leg and GET /balance for the spendable total.
  5. After settlement, request a withdrawal to your approved wallet or pay a customer directly. For USDT or USDC on Ethereum (ERC-20), use ETH as the chain. For USDT on Tron (TRC-20), use TRON. Availability depends on the enabled methods. Each request accepts 1,000 to 100,000,000 USD cents and follows the active withdrawal mode, approval rules and limits.

Withdrawal status

In normal mode, eligible payments can proceed automatically. Approval mode and over-limit payouts can require an administrator’s review. Paused payouts wait until processing resumes. A successful POST acknowledges the request and returns the withdrawal ID.

pending
The request is waiting. It may require review. If reserved is true, the amount is held for this payment.
processing
Payment is underway. Continue tracking the same withdrawal ID.
paid
The payment was confirmed. Use txHash as its blockchain receipt when supplied.
cancelled
The request has been closed. Read the current balance before submitting another payment.

Webhooks

To receive live updates for new API trades and withdrawals, save a public HTTPS callback address in Account settings. During setup, Trade Desk posts {"type":"endpoint.verify","challenge":"…"}. Echo that challenge in the X-Trade-Desk-Challenge response header and return a 2xx response. Save the displayed signing secret on your server.

For subsequent events, verify X-Trade-Desk-Signature with the literal signing secret, the Unix timestamp from X-Trade-Desk-Timestamp, a period and the raw request body bytes. Compare the signature before parsing the JSON. The example accepts a five-minute clock difference.

import { createHmac, timingSafeEqual } from 'node:crypto';

function verifyUpdate(rawBody, timestamp, signature, signingSecret) {
  if (!/^\d+$/.test(timestamp) || !/^[a-f0-9]{64}$/.test(signature)) return false;
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
  const expected = createHmac('sha256', signingSecret)
    .update(timestamp + '.').update(rawBody).digest();
  return timingSafeEqual(expected, Buffer.from(signature, 'hex'));
}
{
  "id": "30000000-0000-4000-8000-000000000003",
  "type": "trade.updated",
  "resourceId": "40000000-0000-4000-8000-000000000004",
  "createdAt": "2026-11-10T00:00:00.000Z"
}

Deduplicate by event ID, queue the update, then acknowledge with 2xx. For trade.updated, read GET /trades/{id}. For withdrawal.updated, read GET /withdrawals/{id}. Use the event’s resourceId for the path ID. Transitions can be combined, and deliveries can be repeated or arrive out of order. Delivery is attempted up to eight times.

Limits & errors

Each endpoint has the per-account limit shown above. The account also shares a budget of 90 requests per minute and three simultaneous requests. IP and service limits apply. A 429 response includes Retry-After in seconds.

400
Invalid request fields or request identity
401
Key is invalid, revoked or unavailable
403
Scope, account policy or rollout stage prevents access
404
Endpoint or account resource is unavailable
405
Method is unsupported
409
Requested terms conflict with account, asset, quote, payout or replay state
413
Request exceeds the size limit
415
JSON content type required
429
Too many requests or simultaneous operations
503
Service temporarily unavailable

Errors return a JSON object with one error string. For a 400, check the fields and formats. For 403, check the key’s permissions. For 409, check the original order, payout terms and available balance. If a request times out or returns 503, retry a trade or withdrawal with its original idempotency key and terms.