Everything the site does, as JSON: read prices and order books, quote and execute buys, sells and swaps,
place and cancel orders, and get every change pushed over a WebSocket the moment it happens.
Market data is public. Anything about your account needs an API key. The API is the same one the site
itself uses, so it always has every feature the site has.
API keys
Send your key in the Authorization header on every request:
Every GET endpoint, POST /api/quote, and your private WebSocket pushes. Every key has it.
trade
Instant trades, placing and cancelling orders, Steam deposits and offer cancels, crypto deposits.
Keys can never move value off traderust. Withdrawing skins to Steam, cashing out crypto, changing your
Steam trade URL and managing keys all need you signed in on the site (session_only). A leaked key can trade, but it can't take anything out.
Revoke a key here and it stops working immediately.
All money is an integer number of mills: 1 mill = $0.001, so 960 is $0.960 and 50000 is $50.
No floats anywhere - send mills, get mills.
Every skin has one order book, and trading is free: no fees on buying, selling, swapping or orders. A trade at $0.941
moves exactly $0.941. ask / bid are the best prices on the book; buy_shown / sell_shown
and a level's shown are what you pay or get trading instantly - today always the same number.
Moving skins to and from Steam is free too. The only fee is on crypto cash-outs: 2%,
taken out of the amount (cash out $50 → receive $49 in crypto) and charged only once the payout is actually sent. Cash-outs need you signed in on
the site - they're never available to API keys.
Exact pricing
You never get less than you were quoted. The flow is always quote → trade:
POST /api/quote with your basket. It returns net: the change to your balance (negative = you pay).
POST /api/trade with the same basket and min_net_mills = that net.
If the book moved against you, nothing trades and you get price_changed - quote again. If it moved in your favour you get the better price.
Quotes only count buyers who can pay at that moment, and a multi-skin quote shares each buyer's balance across every line -
so fillable: true means the whole basket fills unless the book changes before you trade.
Errors
Every error has the same shape, with a stable code to branch on and a human error to show:
{"error": "Prices changed since you looked. Nothing was traded - check the new prices and press again.", "code": "price_changed"}
code
HTTP
Meaning
auth_required
401
No key and not signed in.
bad_key
401
The key is wrong or was revoked.
scope
403
The key lacks the scope this call needs (trade).
session_only
403
Keys can't do this (withdrawals, trade URL, keys) - sign in on the site.
rate_limited
429
Too many requests for this key this minute.
invalid
422
The request body or parameters are malformed - error says which field.
price_changed
400
/api/trade: the book moved against your min_net_mills. Nothing traded - quote again.
would_cross
400
/api/orders: your price would trade instantly. Use a better price or /api/trade.
error
400
Anything else (not enough balance or skins, unknown item, order not found...). error explains.
http_404
404
No such skin / order / offer.
Rate limits
Per key, per minute: 600 read requests and 120 trade requests. Over the limit you get
429 rate_limited until the minute rolls over. Don't poll - subscribe and the changes come to you.
Market data
Public - no key needed. Prices are live order-book prices.
GET/api/marketspublic
List skins with their best prices
Search and page through every skin. With for_sale=1 you get only skins someone is selling right now, filtered and sorted by the best sell order - the same list as the trade page's right-hand side.
Query
Type
q
string
Name contains (case-insensitive).
cat
string
Category, e.g. clothing, weapon, deployable, resource.
sort
string
Column: name · buy · sell · gap · for_sale · wanted · sold (default). Older names price / cheap / volume still work.
dir
string
desc (default; names default to asc) or asc. Skins with no price always sort last.
<col>_min / <col>_max
number
Filter any column: buy_min, buy_max, sell_min, sell_max (mills), gap_min, gap_max (percent), for_sale_min/max, wanted_min/max (skins), sold_min/max (sold last week).
only_book
0|1
1 (default): only skins with at least one open order.
for_sale
0|1
1: only skins with open sell orders; price sorts use the best ask.
price is the price per skin; shown is what you pay (asks) or get (bids) trading instantly - the same, as there are no trading fees. Only orders whose owners can currently pay for them are counted.
steam: [time, Steam median sale price, Steam volume] - Steam prices include Steam's ~15% fee. band: [time, best bid, best ask] snapshots of this market. trades: [time, price, qty, side of the instant trader].
Best ask (lowest sell order) and bid (highest buy order its owner can pay), the quantity behind each, and 7 / 30-day sales (units and median trade price) for every skin with orders or recent trades. Rebuilt every minute; cache it for at least 30 seconds. url links straight to the skin's page by market name.
Volume (24h / 7 days / all time), users, open orders and their value, skins and cash held for users, crypto cash-ins and cash-outs, the site's fee revenue and cash-outs waiting to be paid - aggregates only, never anyone's individual data. Recalculated every minute; 503 warming for the first minute after a restart. Live: WebSocket op stats.
usd.available is cash you can trade; withdrawable is what can be cashed out right now. buy_orders is the total of your open buy orders - they reserve nothing and are paid from available when they fill. items lists every skin you hold on traderust with its live prices.
trades: every fill you were part of (last 100). role is order (your resting order filled) or instant; cash is what it did to your balance. executions: your instant trades / swaps as a whole.
Every fill you were part of, newest first - same shape as /api/history trades, plus icon. more says whether another page exists. The first page (no before) also has totals.
Query
Type
before
integer
Return fills older than this id (the last id of the previous page).
Crypto deposits and cash-outs, and Steam skin deposits and withdrawals, newest first. type is crypto or skins: crypto rows are /api/crypto rows, skins rows are /api/steam/offers rows. The first page also has totals (crypto_out and fees count finished cash-outs; crypto_pending is cash-outs still on their way; skins count completed trades).
Query
Type
kind
string
all (default) · crypto · skins
direction
string
deposit or withdraw (default both)
before
string
ISO timestamp: rows created before it (the last row's created_at).
Quotes need read; everything that changes your balances needs the trade scope.
POST/api/quoteread scope
Price a buy, sell or swap
What you'd get or pay right now for any basket. Nothing is reserved. net is the change to your balance: negative = you pay. fillable is false if the book can't fill every line in full. For a live quote that updates by itself, use the WebSocket quote op.
Request body - Each line is [item_id, qty]. give: skins on traderust. steam_give: skins still in your Steam inventory. get: skins to buy.
Fill-or-kill against the book. min_net_mills is the quote's net you accept as the worst case - pass the quote's net exactly. If prices moved against you nothing trades and you get price_changed; if they moved in your favour you get the better price. With steam_give the trade waits for your Steam deposit (pending: true) and then runs only at min_net_mills or better.
Post-only: an order that would trade instantly is rejected with would_cross - use /api/trade for that. price_mills is per skin, 1-100,000,000; qty 1-10,000. Sell orders set the skins aside. Buy orders set nothing aside - they're paid from your balance when they fill, and an order your balance can't cover at that moment is cancelled (reason: balance).
Moving skins and money in. Withdrawals are never available to API keys.
POST/api/steam/deposittrade scope
Deposit skins from Steam
Sends you a Steam trade offer for these skins (from your Steam inventory). They land in your traderust inventory when you accept it on Steam. Needs your trade URL set on the site.
Request body - Each line is [item_id, qty].
{
"lines": [
[
2457,
1
]
]
}
Response
{
"offer_id": 108
}
GET/api/steam/offersread scope
Your Steam trade offers
status: pending · sent · escrow · cancelling · completed · declined · cancelled · expired · failed. Balances change only on the final state Steam reports.
If Steam reports it was already accepted, the accept wins and the trade completes.
Response
{
"status": "cancelled",
"reason": "cancelled by you"
}
POST/api/crypto/addresstrade scope
Your permanent crypto deposit address
One address per coin, yours permanently: send any amount above min_usd, any time, as often as you like - each transfer is credited by itself, for what actually arrives, once the network confirms it. extra_id is a memo/tag you must include when set. Created the first time you ask for that coin.
Connect to wss://traderust.com/live. Everything is JSON. Order books, your balances and live quotes are pushed
within ~100ms of every change (one push per burst), so there's nothing to poll. Trading itself stays on the REST endpoints above.
You send
{"op": "auth", "key": "trk_…"}
Sign in with a key (or send Authorization: Bearer on the upgrade request). Without it you still get public data.
{"op": "sub", "items": [2457, 1092]}
Live order book, best prices and trades for these skins (up to 600).
{"op": "unsub", "items": [1092]}
Stop.
{"op": "stats"}
The public site stats now (same shape as /api/stats), then again every time they're recalculated (every minute).
A live quote: replied to now, and pushed again every time a skin in it changes - until your next quote op. id is yours; it comes back on every push so you can drop stale ones. Max 50 quote ops per 10 seconds.
If the connection drops, reconnect, then re-send auth, your subscriptions and your live quote. Pushes carry the full
current state, never a diff, so a missed push is fixed by the next one.
Examples
# what's for sale, cheapest first
curl "https://traderust.com/api/markets?for_sale=1&sort=cheap&limit=5"
# quote buying 2 of skin 2457, then buy at exactly that price
curl -X POST https://traderust.com/api/quote -H "Authorization: Bearer $TR_KEY" -H "Content-Type: application/json" \
-d '{"get": [[2457, 2]]}'
curl -X POST https://traderust.com/api/trade -H "Authorization: Bearer $TR_KEY" -H "Content-Type: application/json" \
-d '{"get": [[2457, 2]], "min_net_mills": -1882}'
# rest a buy order at $0.850 each - it fills later, at exactly that price
curl -X POST https://traderust.com/api/orders -H "Authorization: Bearer $TR_KEY" -H "Content-Type: application/json" \
-d '{"item_id": 2457, "side": "buy", "price_mills": 850, "qty": 3}'
import os, requests
API = "https://traderust.com"
s = requests.Session()
s.headers["Authorization"] = "Bearer " + os.environ["TR_KEY"]
def call(method, path, **kw):
r = s.request(method, API + path, **kw)
body = r.json()
if not r.ok:
raise RuntimeError(f"{body['code']}: {body['error']}")
return body
# swap: give one skin you hold, get two of another - priced together
basket = {"give": [[1092, 1]], "get": [[2457, 2]]}
for attempt in range(3):
q = call("POST", "/api/quote", json=basket)
if not q["fillable"]:
raise SystemExit("the book can't fill all of it right now")
print(f"net {q['net'] / 1000:+.3f} USD")
try:
print(call("POST", "/api/trade", json={**basket, "min_net_mills": q["net"]}))
break
except RuntimeError as e:
if "price_changed" not in str(e):
raise # anything else is a real error
# the book moved between quote and trade - quote again
const API = 'https://traderust.com', KEY = process.env.TR_KEY;
const call = async (method, path, body) => {
const r = await fetch(API + path, { method, headers: { Authorization: `Bearer ${KEY}`, 'Content-Type': 'application/json' },
body: body && JSON.stringify(body) });
const j = await r.json();
if (!r.ok) throw Object.assign(new Error(j.error), { code: j.code });
return j;
};
const me = await call('GET', '/api/me');
console.log('balance $' + (me.usd.available / 1000).toFixed(3));
// cancel every open buy order
const { orders } = await call('GET', '/api/orders');
for (const o of orders.filter(o => o.side === 'buy')) await call('DELETE', `/api/orders/${o.id}`);
# pip install websockets
import asyncio, json, os
import websockets
async def main():
async for ws in websockets.connect("wss://traderust.com/live"): # reconnects for you
try:
await ws.send(json.dumps({"op": "auth", "key": os.environ["TR_KEY"]}))
await ws.send(json.dumps({"op": "sub", "items": [2457]}))
# a live quote: pushed again whenever skin 2457's book changes
await ws.send(json.dumps({"op": "quote", "id": 1, "get": [[2457, 1]]}))
async for raw in ws:
m = json.loads(raw)
if m["type"] == "item":
it = m["item"]
print("book", it["bid"], "/", it["ask"], "- buy now", it["buy_shown"])
elif m["type"] == "quote":
print("buying 1 would cost", -m["quote"]["net"], "mills")
elif m["type"] == "me":
print("balance now", m["usd"]["available"])
elif m["type"] == "error":
print("refused:", m["code"], m["error"])
except websockets.ConnectionClosed:
continue
asyncio.run(main())