Liquidity, payments and users for your Rust skins site
One API that lets your site buy skins from its users, sell skins to them, take their crypto and pay them out - at
TradeRust's order-book prices, with no trading fees - while we keep their Steam trade links and profiles for you.
Everything settles in your business balance on TradeRust. Your users never hold money with us: when one sells skins, the
money goes to you and you credit them on your site; when you buy skins for one, you pay. Crypto works the same way - deposits to
your users' addresses are credited to you, payouts to them come out of your balance. Every one of these is a webhook, so your
site can keep its own balances exactly in step.
Fees: none on trading. Crypto payouts cost 2% + the coin's network fee, taken out of the amount, the same as
every TradeRust cash-out. Deposits and skin moves are free.
Quickstart
Open a business account - sign in with Steam, then Business → name it. It's separate from your personal account, with its own balance.
Create a key with the business scope (dashboard → Settings → API keys). It's shown once.
Set your webhook URL and copy the signing secret; add your servers' IPs to the allowlist and your domain for Hosted Connect.
Fund the balance - a crypto deposit from Wallet while switched to the business, or move money from your personal account.
Make your first calls:
export TR=trk_your_business_key
# 1. save a user (your id for them, their SteamID64 and trade URL)
curl -X PUT https://traderust.com/api/biz/users/u_381 -H "Authorization: Bearer $TR" -H "Content-Type: application/json" \
-d '{"steamid": "76561198000000042", "trade_url": "https://steamcommunity.com/tradeoffer/new/?partner=39734314&token=AbCd1234"}'
# 2. what their skins sell for right now
curl https://traderust.com/api/biz/users/u_381/inventory -H "Authorization: Bearer $TR"
# 3. buy two of them from the user for at least $1.760 - they get a Steam trade offer
curl -X POST https://traderust.com/api/biz/sell -H "Authorization: Bearer $TR" -H "Content-Type: application/json" \
-d '{"ref": "sale-1042", "user_ref": "u_381", "items": [[2457, 2]], "min_net_mills": 1760}'
# 4. when they accept you get a sell.completed webhook - or check:
curl https://traderust.com/api/biz/orders/sale-1042 -H "Authorization: Bearer $TR"
Keys & safety
Business keys belong to your business account and carry the business scope - the only kind of TradeRust
key that can send skins or crypto to someone else. That's why a business account has these guards, all under your control on the dashboard:
IP allowlist
When set, every call with your key from any other IP is refused (ip_not_allowed). Set it before going live.
Daily limit
The value that may leave through the API per rolling 24 hours - skins bought for delivery plus crypto paid out. Lowering takes effect at once; raising takes 24 hours, so a stolen key can't empty you. Default $5,000.
Browser-only settings
Keys can't change the webhook URL, allowlist, limit or secret, move money to your personal account, or create keys - those need you signed in (with your 2FA code, if 2FA is on).
Rate limits
600 requests/minute per key; inventory lookups 60/minute and 20,000/day per business. Over: 429 with Retry-After.
Revoke a key on the dashboard and it stops at once. Business keys also work on the regular API (prices, orders, your balance) for the business account.
Conventions
Money
Integer mills: 1000 = $1.000, 891 = $0.891. Never floats. Divide by 1000 to show dollars.
ref - your order id, and safe retries
Every sell, buy and payout carries ref: your own id for it (1-64 chars, A-Z a-z 0-9 _ . : @ -), unique per business.
Sending the same ref again returns the existing order ("created": false) instead of making a second one - so after a timeout,
just retry with the same ref. Look anything up later by ref.
user_ref - your id for a user
Save users once with PUT /api/biz/users/{user_ref} (or Hosted Connect), then send just user_ref on orders and payouts.
You can also send steamid + trade_url on an order directly.
Items
Either [[item_id, qty], …] or [{"market_hash_name": "Hellforged Vest", "qty": 2}, …] - Steam's own names work.
Up to 50 different skins per order (one Steam trade offer).
Paging
Lists return next. Newest first: pass before=<next>. To sync from a point: after=<id>, oldest first.
Times & errors
Timestamps are UTC ISO-8601 with Z. Errors are {"error": "…", "code": "…"} plus extra fields for some codes - see Errors.
Sandbox
sandbox.traderust.com is a full copy of TradeRust where nothing is real: Steam trade offers and crypto
are simulated, order books are seeded with test depth, and it can't reach real Steam accounts or wallets. Sign in with Steam there,
open a business account and create a key exactly as on the live site; use https://sandbox.traderust.com as the base URL.
Because nobody is on the other end of a sandbox trade offer, you play their part with these extra endpoints (sandbox only):
POST /api/biz/sandbox/offers/{offer_id}/accept · /decline
Your user accepts or declines a trade offer (the order's trade_offer.id / return_offer.id).
POST /api/biz/sandbox/crypto/pay{"address", "usd_mills"}
Money arrives at one of your users' deposit addresses.
POST /api/biz/sandbox/prices/move{"item_id", "percent"}
Move a skin's buy orders, to see price_changed.
POST /api/biz/sandbox/fund{"usd_mills"}
Top up your sandbox balance.
Payouts finish by themselves a few seconds after you request them. Webhooks are sent for real, to your real webhook URL.
Example site: Rust Demo Market
Rust Demo Market is a small skins site built only on this API, running
against the sandbox. Sign in with a test Steam account, sell skins from a fake inventory, buy from the market, deposit and withdraw
crypto - with a live console beside it showing every API call it makes and every webhook it receives, signature checked.
POST /api/biz/connect-link → Hosted Connect → signed return
Sell skins
GET /api/biz/users/{user_ref}/inventory, POST /api/biz/quote, POST /api/biz/sell; sell.completed webhook credits the balance
Shop
GET /api/biz/market, POST /api/biz/buy; buy.delivered
Wallet
POST /api/biz/crypto/address, POST /api/biz/crypto/withdraw; crypto.deposit, crypto.withdraw_finished
History
GET /api/biz/users/{user_ref}/history
Guide: your users
Two ways to get a user into your directory:
You already have their Steam ID and trade URL → PUT /api/biz/users/{user_ref}. We check the trade URL belongs to that Steam ID.
You don't → send them through Hosted Connect: they sign in with Steam on TradeRust, paste their trade URL (we show them where to find it), and come back to your site signed. You get user.connected.
We then read their public Steam profile - name, avatars, privacy, whether it's a limited account, trade ban and VAC status, member-since - and keep it fresh weekly
(POST …/refresh to force it). A trade_ban other than "None" or a limited_account usually means trade offers will fail.
GET /api/biz/users/{user_ref}/history is their whole story with you: every order and crypto transfer and the totals. Erasing a user
(DELETE) wipes their personal data but keeps the money records under the user_ref.
Guide: inventories & prices
GET /api/biz/users/{user_ref}/inventory returns every tradable skin in their Steam inventory that TradeRust trades, with
instant_sell_mills - what one sells for right now - and instant_sell_depth, how many the book would take at that price.
For a basket, always quote: POST /api/biz/quote walks the book exactly as the trade will, so selling 5 of something deep
in the book is priced correctly. Show your user that total, and send it as min_net_mills.
Inventories are cached 60 seconds; ?refresh=1 re-reads Steam (once per 30s per Steam ID). A private inventory gives
502 inventory_unavailable - ask the user to make it public.
Guide: buying skins from your users
Your user sells; you receive the money. POST /api/biz/sell with the quoted total as min_net_mills:
offer_sent→ user accepts →completedreceived_mills added to your balance offer_sent→ declines / ignores 10 min / you cancel →declined · expired · cancelled · failednothing moved offer_sent→ accepts, but prices fell below min_net →returning→returnedon_fail "return" heldon_fail "hold", or the return failed - POST …/return
Steam trade offers expire after 10 minutes. Tell your user to accept in Steam (trade_offer.url opens it); the offer message names your business.
The skins sell the moment they arrive, at min_net_mills or better - never worse. If the book fell meanwhile, nothing sells.
on_fail: "return" (default) sends them straight back - right for most sites. "hold" keeps them in your vault: re-quote and let your user decide, then POST …/return or sell them yourself on the regular API.
On sell.completed, credit your user received_mills.
Guide: selling skins to your users
You pay; your user gets skins. POST /api/biz/buy with the most you'll pay as max_cost_mills:
offer_sent→ user accepts →delivered offer_sent→ declines / expires →delivery_failed→ POST …/retry →offer_sent
The purchase happens immediately, fill-or-kill: either every skin is bought at max_cost_mills or less, or nothing is (409 price_changed, with a fresh quote). paid_mills is what it cost.
We then send the skins to your user's Steam. Debit your user when you create the order (or on buy.offer_sent).
If they decline, the skins are back in your vault: retry (optionally with a new trade URL), sell them again, or keep them.
Buys count toward your daily limit.
Guide: crypto deposits for your users
POST /api/biz/crypto/address {"user_ref", "coin"} gives that user a permanent address for the coin. Show it to them; they can send
any amount above the coin's minimum (GET /api/biz/crypto/coins), any number of times. Each deposit is credited to your balance at its
USD value on arrival and sent to you as crypto.deposit with the user_ref and amount_mills - credit your user that.
Coins that need a memo (TON, …) return extra_id: your user must include it.
Deposits below the coin's minimum aren't credited (that's the payment provider's rule, not ours) - show the minimum next to the address.
Guide: crypto payouts
POST /api/biz/crypto/withdraw {"ref", "user_ref", "coin", "address", "usd_mills"} debits usd_mills from your balance and sends it
within seconds - there's no cancel window for business payouts. The fee comes out of the amount: 2% + the coin's network fee.
Example: $50.000 as USDT on Solana → fee $1.260 → payout_mills 48740 arrives. Minimum $5.
Then: crypto.withdraw_sent → crypto.withdraw_finished (with tx_hash), or crypto.withdraw_failed with the full
amount back in your balance. Don't re-send on a timeout - retry with the same ref.
Guide: reconciling
Your site's books should match ours. Daily: sum your users' credits/debits from events, compare with GET /api/biz/account's balance
movement, and page through GET /api/biz/events?after=… for anything your webhook missed. /api/biz/orders and
/api/biz/crypto/transfers page by id for exports; /api/biz/users/export?format=csv dumps the directory.
Webhooks
Set a URL on the dashboard and every change is POSTed to it as JSON:
Answer 2xx within 10 seconds. Anything else is retried after 30s, 120s, 600s, 1800s, 3600s, 10800s, 21600s, 43200s (about a day in all), then left in the event feed.
Dedupe on X-TradeRust-Id (= id): a retry repeats it. Events can arrive out of order across retries - trust the object's status.
Nothing is ever lost: each event is written in the same database transaction as the change itself. Missed some? GET /api/biz/events?after=<last id>.
Webhook URLs must be https on a public host.
Event types
type
data
What to do
sell.offer_sent
order
The deposit offer is on Steam. Show your user trade_offer.url (or tell them to check Steam).
sell.completed
order
Sold. received_mills is now in your balance - credit your user.
sell.price_changed
order
They accepted but the book fell below min_net_mills. status is returning (on_fail return) or held.
sell.returned
order
The skins are back in your user's Steam inventory.
sell.return_failed
order
The return offer didn't go through; the skins are held - POST …/return to try again.
The deposit offer ended without the skins arriving. Nothing moved. reason says why.
buy.offer_sent
order
Delivery offer is on Steam (you've already paid - paid_mills).
buy.delivered
order
Your user has the skins.
buy.delivery_failed
order
Declined/expired/failed; the skins are back in your vault. Retry, resell, or leave them.
crypto.deposit
transfer + amount_mills
A deposit to one of your users' addresses was credited to you. Credit amount_mills to user_ref.
crypto.withdraw_sent
transfer
The payout is on its way.
crypto.withdraw_finished
transfer
Paid - tx_hash is set.
crypto.withdraw_failed
transfer
Refused by the network/provider; debited_mills is back in your balance.
crypto.withdraw_review
transfer
Stuck at the provider; our team is checking it. Don't pay the user again.
user.created / user.updated
user
A directory entry was added or changed through the API.
user.connected
user
A user finished Hosted Connect (steamid + trade URL saved).
user.erased
{user_ref}
You erased a user.
user.trade_url_invalid
{user_ref, steamid, reason, order_ref}
Steam refused an offer to them - their trade URL is probably stale. Ask for a new one.
business.limit_raise_requested
{daily_limit_mills, raise}
Someone raised your daily limit; it takes effect at raise.at. If it wasn't you, lower it and rotate your keys.
test.ping
{message}
From the dashboard's or the API's test button.
Order events carry the full order (as GET /api/biz/orders/{ref}); crypto events the full transfer.
Verifying signatures
v1 is HMAC-SHA256, keyed with your signing secret, of "<t>." + the raw body. Reject a request whose signature doesn't
match or whose t is more than 5 minutes off. Use the raw body bytes - re-serialised JSON won't match.
import hashlib, hmac, time
def verify_traderust(secret: str, header: str, body: bytes, tolerance: int = 300) -> bool:
"""header = the X-TradeRust-Signature value, body = the raw request body (bytes)."""
try:
parts = dict(p.split("=", 1) for p in header.split(","))
ts = int(parts["t"])
except (ValueError, KeyError):
return False
if abs(time.time() - ts) > tolerance: # stale: a replayed request
return False
expected = hmac.new(secret.encode(), f"{ts}.".encode() + body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))
const crypto = require('crypto');
// header = req.headers['x-traderust-signature'], rawBody = the raw body as a Buffer
// (e.g. express.raw({ type: 'application/json' })) - NOT re-serialised JSON.
function verifyTradeRust(secret, header, rawBody, tolerance = 300) {
const parts = Object.fromEntries(String(header || '').split(',').map(p => p.split('=')));
const ts = parseInt(parts.t, 10);
if (!ts || Math.abs(Date.now() / 1000 - ts) > tolerance) return false;
const expected = crypto.createHmac('sha256', secret).update(`${ts}.`).update(rawBody).digest('hex');
return typeof parts.v1 === 'string' && parts.v1.length === expected.length
&& crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}
package traderust
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"strconv"
"strings"
"time"
)
// VerifySignature: header = r.Header.Get("X-TradeRust-Signature"), body = the raw request body.
func VerifySignature(secret, header string, body []byte, tolerance time.Duration) bool {
var ts int64
var sig string
for _, p := range strings.Split(header, ",") {
kv := strings.SplitN(p, "=", 2)
if len(kv) != 2 {
continue
}
switch kv[0] {
case "t":
ts, _ = strconv.ParseInt(kv[1], 10, 64)
case "v1":
sig = kv[1]
}
}
if ts == 0 {
return false
}
if d := time.Since(time.Unix(ts, 0)); d > tolerance || d < -tolerance {
return false
}
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(strconv.FormatInt(ts, 10) + "."))
mac.Write(body)
return hmac.Equal([]byte(hex.EncodeToString(mac.Sum(nil))), []byte(sig))
}
A complete handler
# Flask: credit your users from TradeRust's webhooks - exactly once each.
from flask import Flask, abort, request
app = Flask(__name__)
SECRET = "whsec_…" # Business dashboard -> Settings -> Show signing secret
@app.post("/traderust/webhook")
def traderust_webhook():
if not verify_traderust(SECRET, request.headers.get("X-TradeRust-Signature", ""), request.get_data()):
abort(400)
event = request.get_json()
with db.transaction():
if db.seen_event(event["id"]): # retries repeat the same id - process each once
return "", 200
db.mark_event_seen(event["id"])
d = event["data"]
if event["type"] == "sell.completed":
db.credit(d["user_ref"], d["received_mills"], reason=f"skins sold ({d['ref']})")
elif event["type"] == "crypto.deposit":
db.credit(d["user_ref"], d["amount_mills"], reason=f"{d['coin']} deposit")
elif event["type"] == "buy.delivery_failed":
db.flag_for_retry(d["ref"])
return "", 200 # any 2xx = delivered; anything else is retried
Hosted Connect
Skip building Steam sign-in and a trade-URL form: send your user to TradeRust and they come back with both saved in your directory.
Make a link - POST /api/biz/connect-link, or sign it yourself (below). Links work for 30 minutes.
The user signs in through Steam (their password only ever goes to Steam), sees your business name, and pastes their trade URL.
They're sent to your return URL with tr_user_ref, tr_steamid, tr_ts, tr_sig - check the signature - or tr_error=cancelled. You also get user.connected.
return must be https on one of your domains (dashboard → Settings → Hosted Connect). The signing secret is your webhook secret.
import hashlib, hmac, time
from urllib.parse import urlencode
def connect_link(public_id: str, secret: str, user_ref: str, return_url: str) -> str:
ts = str(int(time.time()))
msg = ".".join(["connect", public_id, user_ref, return_url, ts])
sig = hmac.new(secret.encode(), msg.encode(), hashlib.sha256).hexdigest()
return "https://traderust.com/connect?" + urlencode(
{"b": public_id, "user_ref": user_ref, "return": return_url, "ts": ts, "sig": sig})
def connected_ok(secret: str, q: dict, max_age: int = 600) -> bool:
"""q = the query parameters TradeRust sent your user back with."""
if "tr_sig" not in q or abs(time.time() - int(q.get("tr_ts", 0))) > max_age:
return False
msg = ".".join(["connected", q["tr_user_ref"], q["tr_steamid"], q["tr_ts"]])
want = hmac.new(secret.encode(), msg.encode(), hashlib.sha256).hexdigest()
return hmac.compare_digest(want, q["tr_sig"])
Account
Your business balance and limits.
GET/api/biz/account
Balance, limit and what's left today
outflow_24h_mills is the value that left through the API in the last 24 hours (skins bought for delivery + crypto paid out); outflow_left_mills is what your daily limit still allows. raise shows a pending limit increase.
What skins sell and cost for right now, and your users' Steam inventories.
GET/api/biz/market
Every skin's best bid and ask
bid_mills is what one sells for instantly (the best buy order whose owner can pay), ask_mills what one costs (the best sell order); the qty fields say how many are behind each. Cached 10 seconds - quote a basket for exact totals.
The tradable Rust skins in any public Steam inventory, as TradeRust items: qty, instant_sell_mills (one, right now) and instant_sell_depth (how many the book would buy at that price). untracked counts skins we don't trade. 60/minute and 20,000/day per business. A private inventory is 502 inventory_unavailable.
Query
Type
refresh
bool
Skip the 60-second cache (at most once per 30s per steamid).
side sell: what your user's skins would sell for into the bids. side buy: what buying them from the asks costs. fillable false means the book can't take/give all of it. Read-only - send the total you show your user as min_net_mills / max_cost_mills.
Buying skins from your users and selling skins to them.
POST/api/biz/sell
Buy skins from your user
Your user sells skins from their Steam inventory. We send THEM a Steam trade offer for the skins; when they accept, the skins sell into the book at min_net_mills or better and the money is added to your balance (sell.completed). If the book can't pay min_net_mills right now the order isn't created (409 price_changed, with a fresh quote). If prices fall while the offer is open: on_fail "return" (default) sends the skins back, "hold" keeps them in your vault. Identify the user by user_ref (from your directory) or steamid + trade_url (saved to the directory if you also send user_ref). 201 = created; 200 with created false = this ref already existed (safe retry).
Request body - items: [[item_id, qty]] or [{"market_hash_name": …, "qty": n}]
Buys from the cheapest sell orders - fill-or-kill, paid from your balance, never more than max_cost_mills (else 409 price_changed) - and sends them to your user in a Steam trade offer. Counts toward your daily limit. If they decline or let it expire (buy.delivery_failed) the skins are back in your vault: retry, sell them, or leave them.
Asks Steam to cancel. The outcome arrives as an event: a user who accepted a moment before still completes (Steam's word is final). 409 not_cancellable once the offer has finished.
The directory we keep for you, and Hosted Connect.
PUT/api/biz/users/{user_ref}
Add or update a user
user_ref is your own id for them (1-64 of A-Z a-z 0-9 _ . : @ -). The trade URL must belong to the steamid. A new steamid clears the old trade URL and profile. metadata is yours (JSON object, up to 4096 bytes). We fetch their public Steam profile in the background and refresh it weekly.
Wipes their steamid, trade URL, profile and metadata (e.g. a GDPR request). Their orders and payments stay, under user_ref only - you need them for your books.
Response
{
"ok": true,
"erased": "u_381"
}
POST/api/biz/connect-link
A Hosted Connect link for a user
Send your user to url (valid 30 minutes). They sign in with Steam and give their trade URL on TradeRust, then come back to return_url - which must be https on one of your domains - with tr_user_ref, tr_steamid, tr_ts and tr_sig. You can also build links yourself (see Hosted Connect).
Deposit addresses for your users, and payouts to them.
GET/api/biz/crypto/coins
Coins, fees and minimums
network_fee_mills is what sending that coin costs (passed on at cost, refreshed hourly). min_deposit_usd is the smallest deposit our payment provider accepts for the coin right now - smaller ones aren't credited.
Permanent: the same user_ref + coin always gets the same address, and every deposit to it - any amount over the minimum, any number of times - is credited to your balance and reported as crypto.deposit with the user_ref.
Debits usd_mills from your balance now and sends it within seconds - no cancel window. The fee (2% + the coin's network fee) comes out of it: payout_mills is what arrives. Idempotent by ref. Counts toward your daily limit. Events: crypto.withdraw_sent, then crypto.withdraw_finished (with tx_hash) or crypto.withdraw_failed (refunded in full).
Request body - extra_id for coins that need a memo/tag
Everything that happened, as signed webhooks or a feed you can page through.
GET/api/biz/events
The event feed
Every event we've sent (or tried to send) you, with its delivery state. Poll after=<last id you processed> if you can't receive webhooks, or to catch up after downtime.
Everything works end-to-end in the sandbox, including a declined offer, a price change and a failed payout.
Your webhook handler verifies signatures, dedupes by event id, and answers 2xx fast (do slow work after).
Every create call sends a ref, and your code retries with the same ref.
IP allowlist set to your servers; daily limit set to what you really need.
Key stored as a secret (never in the browser or a repo); a second key ready to rotate.
Your users see the price before they confirm, and you send it as min_net_mills / max_cost_mills.
You poll /api/biz/events?after= once on startup to catch anything missed while you were down.
FAQ
Where does the liquidity come from?
TradeRust's order books: every buy and sell order on the site, from traders and other businesses. Your sells fill the best buy orders, your buys the best sell orders.
You can add depth yourself - place resting orders with your business key on the regular API.
Can a price change after my user agrees?
Not against them. A sell never executes below min_net_mills and a buy never above max_cost_mills. If the book moves the wrong way, the sell doesn't execute (skins go back or are held) and the buy is refused before anything is paid.
Is there a Steam trade hold?
Rust skins have no Steam trade hold: accepted offers complete at once.
What does my user see?
A Steam trade offer from the TradeRust vault bot, with a message naming your business and the order number. On Hosted Connect, your business name.
Can I see trades in my dashboard?
Yes - Business shows orders, users, crypto and every webhook with its delivery state. Your business account's balance, orders and history also show on the normal pages while you're switched to it.
Support
Support - include your business ID (b_…) and the ref.
Changelog
2026-11
First release: skins orders, user directory, Hosted Connect, crypto deposits and payouts, webhooks, sandbox.