SANDBOX - Steam trade offers and crypto are simulated; nothing here is real. How the sandbox works · Go to TradeRust
TRADERUST
Network
TradeRust (you're here) Rust Wiki Server browser Inventory value Blog
Sign in with Steam
TradeRust Business API · 2026-11 · SANDBOX

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.

Base URLhttps://sandbox.traderust.com
AuthAuthorization: Bearer trk_… (business scope)
Moneyinteger mills · 1 USD = 1000

The money model

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.

Skins inuser's Steam → our buy orders → your balance +POST /api/biz/sell
Skins outyour balance − → our sell orders → user's SteamPOST /api/biz/buy
Crypto inuser's wallet → their address → your balance +POST /api/biz/crypto/address
Crypto outyour balance − → user's walletPOST /api/biz/crypto/withdraw

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

  1. Open a business account - sign in with Steam, then Business → name it. It's separate from your personal account, with its own balance.
  2. Create a key with the business scope (dashboard → Settings → API keys). It's shown once.
  3. Set your webhook URL and copy the signing secret; add your servers' IPs to the allowlist and your domain for Hosted Connect.
  4. Fund the balance - a crypto deposit from Wallet while switched to the business, or move money from your personal account.
  5. 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://sandbox.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://sandbox.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://sandbox.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://sandbox.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 allowlistWhen set, every call with your key from any other IP is refused (ip_not_allowed). Set it before going live.
Daily limitThe 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 settingsKeys 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 limits600 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 · /declineYour 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.

ScreenAPI calls
Sign inPOST /api/biz/connect-link → Hosted Connect → signed return
Sell skinsGET /api/biz/users/{user_ref}/inventory, POST /api/biz/quote, POST /api/biz/sell; sell.completed webhook credits the balance
ShopGET /api/biz/market, POST /api/biz/buy; buy.delivered
WalletPOST /api/biz/crypto/address, POST /api/biz/crypto/withdraw; crypto.deposit, crypto.withdraw_finished
HistoryGET /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
  1. 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.
  2. The skins sell the moment they arrive, at min_net_mills or better - never worse. If the book fell meanwhile, nothing sells.
  3. 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.
  4. 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
  1. 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.
  2. We then send the skins to your user's Steam. Debit your user when you create the order (or on buy.offer_sent).
  3. If they decline, the skins are back in your vault: retry (optionally with a new trade URL), sell them again, or keep them.
  4. 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:

POST /your/webhook
Content-Type: application/json
X-TradeRust-Event: sell.completed
X-TradeRust-Id: 1201
X-TradeRust-Signature: t=1762697000,v1=5f2c…

{
  "id": 1201,
  "type": "sell.completed",
  "api_version": "2026-11",
  "created_at": "2026-11-09T14:03:11.204Z",
  "data": {
    "ref": "sale-1042",
    "id": 17,
    "side": "sell",
    "status": "completed",
    "user_ref": "u_381",
    "steamid": "76561198000000042",
    "items": [
      {
        "item_id": 2457,
        "name": "Hellforged Vest",
        "market_hash_name": "Hellforged Vest",
        "icon": "https://files.facepunch.com/rust/icons/inventory/rust/70010_small.png",
        "qty": 2
      }
    ],
    "min_net_mills": 1760,
    "received_mills": 1782,
    "fills": [
      {
        "item_id": 2457,
        "name": "Hellforged Vest",
        "side": "sell",
        "qty": 2,
        "total": 1782,
        "avg_shown": 891
      }
    ],
    "trade_offer": {
      "id": 311,
      "status": "completed",
      "steam_offer_id": "7712345678",
      "url": "https://steamcommunity.com/tradeoffer/7712345678/",
      "expires_at": "2026-11-09T14:13:11.204Z",
      "reason": null
    },
    "reason": null,
    "created_at": "2026-11-09T14:03:11.204Z",
    "updated_at": "2026-11-09T14:03:11.204Z",
    "on_fail": "return",
    "return_offer": null
  }
}
  • 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

typedataWhat to do
sell.offer_sentorderThe deposit offer is on Steam. Show your user trade_offer.url (or tell them to check Steam).
sell.completedorderSold. received_mills is now in your balance - credit your user.
sell.price_changedorderThey accepted but the book fell below min_net_mills. status is returning (on_fail return) or held.
sell.returnedorderThe skins are back in your user's Steam inventory.
sell.return_failedorderThe return offer didn't go through; the skins are held - POST …/return to try again.
sell.declined / sell.expired / sell.cancelled / sell.failedorderThe deposit offer ended without the skins arriving. Nothing moved. reason says why.
buy.offer_sentorderDelivery offer is on Steam (you've already paid - paid_mills).
buy.deliveredorderYour user has the skins.
buy.delivery_failedorderDeclined/expired/failed; the skins are back in your vault. Retry, resell, or leave them.
crypto.deposittransfer + amount_millsA deposit to one of your users' addresses was credited to you. Credit amount_mills to user_ref.
crypto.withdraw_senttransferThe payout is on its way.
crypto.withdraw_finishedtransferPaid - tx_hash is set.
crypto.withdraw_failedtransferRefused by the network/provider; debited_mills is back in your balance.
crypto.withdraw_reviewtransferStuck at the provider; our team is checking it. Don't pay the user again.
user.created / user.updateduserA directory entry was added or changed through the API.
user.connecteduserA 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", ""))

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.

  1. Make a link - POST /api/biz/connect-link, or sign it yourself (below). Links work for 30 minutes.
  2. The user signs in through Steam (their password only ever goes to Steam), sees your business name, and pastes their trade URL.
  3. 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.

Response
{
  "business": {
    "id": 52,
    "public_id": "b_3f9a1c2d7e4b",
    "name": "RustDrop",
    "website": "https://rustdrop.gg",
    "status": "active",
    "webhook_url": "https://rustdrop.gg/hooks/traderust",
    "ip_allowlist": [
      "203.0.113.7/32"
    ],
    "domains": [
      "rustdrop.gg"
    ],
    "daily_limit_mills": 5000000,
    "raise": null,
    "created_at": "2026-11-09T14:03:11.204Z"
  },
  "balance": {
    "available_mills": 1250000,
    "locked_mills": 0,
    "withdrawable_mills": 1250000
  },
  "outflow_24h_mills": 84120,
  "outflow_left_mills": 4915880
}

Prices & inventories

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.

Response
{
  "updated_at": "2026-11-09T14:03:11.204Z",
  "unit": "mills",
  "items": [
    {
      "item_id": 2457,
      "market_hash_name": "Hellforged Vest",
      "name": "Hellforged Vest",
      "icon": "https://\u2026",
      "category": "clothing",
      "bid_mills": 891,
      "bid_qty": 19,
      "ask_mills": 941,
      "ask_qty": 23
    }
  ]
}
GET/api/biz/inventory/{steamid}

A Steam user's skins, with instant-sell prices

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.

QueryType
refreshboolSkip the 60-second cache (at most once per 30s per steamid).
Response
{
  "steamid": "76561198000000042",
  "items": [
    {
      "id": 2457,
      "name": "Hellforged Vest",
      "icon": "https://\u2026",
      "category": "clothing",
      "fair": 905,
      "weekly_sales": 79,
      "ask": 941,
      "ask_qty": 23,
      "bid": 891,
      "bid_qty": 19,
      "buy_shown": 941,
      "sell_shown": 891,
      "market_hash_name": "Hellforged Vest",
      "qty": 2,
      "instant_sell_mills": 891,
      "instant_sell_depth": 19
    }
  ],
  "untracked": 3,
  "total_instant_sell_mills": 1782,
  "fetched_at": 1762697000,
  "refreshed": false,
  "retry_in": 0
}
GET/api/biz/users/{user_ref}/inventory

The same, for a user in your directory

QueryType
refreshboolAs above.
Response
{
  "user_ref": "u_381",
  "steamid": "76561198000000042",
  "items": [
    {
      "id": 2457,
      "name": "Hellforged Vest",
      "icon": "https://\u2026",
      "category": "clothing",
      "fair": 905,
      "weekly_sales": 79,
      "ask": 941,
      "ask_qty": 23,
      "bid": 891,
      "bid_qty": 19,
      "buy_shown": 941,
      "sell_shown": 891,
      "market_hash_name": "Hellforged Vest",
      "qty": 2,
      "instant_sell_mills": 891,
      "instant_sell_depth": 19
    }
  ],
  "untracked": 3,
  "total_instant_sell_mills": 1782,
  "fetched_at": 1762697000,
  "refreshed": false,
  "retry_in": 0
}
POST/api/biz/quote

Exact total for a basket, right now

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.

Request body
{
  "side": "sell",
  "items": [
    [
      2457,
      2
    ]
  ]
}
Response
{
  "side": "sell",
  "lines": [
    {
      "item_id": 2457,
      "name": "Hellforged Vest",
      "qty": 2,
      "fillable": 2,
      "total_mills": 1782,
      "unit_mills": 891,
      "best_mills": 891,
      "worst_mills": 891
    }
  ],
  "total_mills": 1782,
  "fillable": true
}

Skins orders

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}]
{
  "ref": "sale-1042",
  "user_ref": "u_381",
  "items": [
    [
      2457,
      2
    ]
  ],
  "min_net_mills": 1760,
  "on_fail": "return"
}
Response
{
  "order": {
    "ref": "sale-1042",
    "id": 17,
    "side": "sell",
    "status": "offer_sent",
    "user_ref": "u_381",
    "steamid": "76561198000000042",
    "items": [
      {
        "item_id": 2457,
        "name": "Hellforged Vest",
        "market_hash_name": "Hellforged Vest",
        "icon": "https://files.facepunch.com/rust/icons/inventory/rust/70010_small.png",
        "qty": 2
      }
    ],
    "min_net_mills": 1760,
    "received_mills": null,
    "fills": null,
    "trade_offer": {
      "id": 311,
      "status": "sent",
      "steam_offer_id": "7712345678",
      "url": "https://steamcommunity.com/tradeoffer/7712345678/",
      "expires_at": "2026-11-09T14:13:11.204Z",
      "reason": null
    },
    "reason": null,
    "created_at": "2026-11-09T14:03:11.204Z",
    "updated_at": "2026-11-09T14:03:11.204Z",
    "on_fail": "return",
    "return_offer": null
  },
  "created": true
}
POST/api/biz/buy

Sell skins to your user

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.

Request body
{
  "ref": "order-5521",
  "user_ref": "u_381",
  "items": [
    {
      "market_hash_name": "Hellforged Vest",
      "qty": 1
    }
  ],
  "max_cost_mills": 1000
}
Response
{
  "order": {
    "ref": "order-5521",
    "id": 18,
    "side": "buy",
    "status": "offer_sent",
    "user_ref": "u_381",
    "steamid": "76561198000000042",
    "items": [
      {
        "item_id": 2457,
        "name": "Hellforged Vest",
        "market_hash_name": "Hellforged Vest",
        "icon": "https://files.facepunch.com/rust/icons/inventory/rust/70010_small.png",
        "qty": 1
      }
    ],
    "max_cost_mills": 1000,
    "paid_mills": 941,
    "fills": [
      {
        "item_id": 2457,
        "name": "Hellforged Vest",
        "side": "buy",
        "qty": 1,
        "total": 941,
        "avg_shown": 941
      }
    ],
    "trade_offer": {
      "id": 312,
      "status": "sent",
      "steam_offer_id": "7712345701",
      "url": "https://steamcommunity.com/tradeoffer/7712345701/",
      "expires_at": "2026-11-09T14:13:11.204Z",
      "reason": null
    },
    "reason": null,
    "created_at": "2026-11-09T14:03:11.204Z",
    "updated_at": "2026-11-09T14:03:11.204Z"
  },
  "created": true
}
GET/api/biz/orders

Your orders

QueryType
statusstringComma-separated statuses.
sidestringsell or buy.
user_refstringOne user's.
before / afterintPage by order id: newest first with before=<next>, or oldest first after=<id> to sync.
limitint1-200, default 50.
Response
{
  "orders": [
    {
      "ref": "sale-1042",
      "id": 17,
      "side": "sell",
      "status": "completed",
      "user_ref": "u_381",
      "steamid": "76561198000000042",
      "items": [
        {
          "item_id": 2457,
          "name": "Hellforged Vest",
          "market_hash_name": "Hellforged Vest",
          "icon": "https://files.facepunch.com/rust/icons/inventory/rust/70010_small.png",
          "qty": 2
        }
      ],
      "min_net_mills": 1760,
      "received_mills": 1782,
      "fills": [
        {
          "item_id": 2457,
          "name": "Hellforged Vest",
          "side": "sell",
          "qty": 2,
          "total": 1782,
          "avg_shown": 891
        }
      ],
      "trade_offer": {
        "id": 311,
        "status": "completed",
        "steam_offer_id": "7712345678",
        "url": "https://steamcommunity.com/tradeoffer/7712345678/",
        "expires_at": "2026-11-09T14:13:11.204Z",
        "reason": null
      },
      "reason": null,
      "created_at": "2026-11-09T14:03:11.204Z",
      "updated_at": "2026-11-09T14:03:11.204Z",
      "on_fail": "return",
      "return_offer": null
    }
  ],
  "next": null
}
GET/api/biz/orders/{ref}

One order

Response
{
  "order": {
    "ref": "sale-1042",
    "id": 17,
    "side": "sell",
    "status": "completed",
    "user_ref": "u_381",
    "steamid": "76561198000000042",
    "items": [
      {
        "item_id": 2457,
        "name": "Hellforged Vest",
        "market_hash_name": "Hellforged Vest",
        "icon": "https://files.facepunch.com/rust/icons/inventory/rust/70010_small.png",
        "qty": 2
      }
    ],
    "min_net_mills": 1760,
    "received_mills": 1782,
    "fills": [
      {
        "item_id": 2457,
        "name": "Hellforged Vest",
        "side": "sell",
        "qty": 2,
        "total": 1782,
        "avg_shown": 891
      }
    ],
    "trade_offer": {
      "id": 311,
      "status": "completed",
      "steam_offer_id": "7712345678",
      "url": "https://steamcommunity.com/tradeoffer/7712345678/",
      "expires_at": "2026-11-09T14:13:11.204Z",
      "reason": null
    },
    "reason": null,
    "created_at": "2026-11-09T14:03:11.204Z",
    "updated_at": "2026-11-09T14:03:11.204Z",
    "on_fail": "return",
    "return_offer": null
  }
}
POST/api/biz/orders/{ref}/cancel

Cancel the open Steam offer

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.

Response
{
  "order": {
    "ref": "sale-1042",
    "id": 17,
    "side": "sell",
    "status": "cancelled",
    "user_ref": "u_381",
    "steamid": "76561198000000042",
    "items": [
      {
        "item_id": 2457,
        "name": "Hellforged Vest",
        "market_hash_name": "Hellforged Vest",
        "icon": "https://files.facepunch.com/rust/icons/inventory/rust/70010_small.png",
        "qty": 2
      }
    ],
    "min_net_mills": 1760,
    "received_mills": null,
    "fills": null,
    "trade_offer": {
      "id": 311,
      "status": "sent",
      "steam_offer_id": "7712345678",
      "url": "https://steamcommunity.com/tradeoffer/7712345678/",
      "expires_at": "2026-11-09T14:13:11.204Z",
      "reason": null
    },
    "reason": null,
    "created_at": "2026-11-09T14:03:11.204Z",
    "updated_at": "2026-11-09T14:03:11.204Z",
    "on_fail": "return",
    "return_offer": null
  }
}
POST/api/biz/orders/{ref}/retry

Deliver a failed buy again

For a buy in delivery_failed. Optionally send a new trade_url for the same Steam account.

Request body - optional
{
  "trade_url": "https://steamcommunity.com/tradeoffer/new/?partner=39734314\u0026token=NewTok12"
}
Response
{
  "order": {
    "ref": "order-5521",
    "id": 18,
    "side": "buy",
    "status": "offer_sent",
    "user_ref": "u_381",
    "steamid": "76561198000000042",
    "items": [
      {
        "item_id": 2457,
        "name": "Hellforged Vest",
        "market_hash_name": "Hellforged Vest",
        "icon": "https://files.facepunch.com/rust/icons/inventory/rust/70010_small.png",
        "qty": 1
      }
    ],
    "max_cost_mills": 1000,
    "paid_mills": 941,
    "fills": [
      {
        "item_id": 2457,
        "name": "Hellforged Vest",
        "side": "buy",
        "qty": 1,
        "total": 941,
        "avg_shown": 941
      }
    ],
    "trade_offer": {
      "id": 312,
      "status": "sent",
      "steam_offer_id": "7712345701",
      "url": "https://steamcommunity.com/tradeoffer/7712345701/",
      "expires_at": "2026-11-09T14:13:11.204Z",
      "reason": null
    },
    "reason": null,
    "created_at": "2026-11-09T14:03:11.204Z",
    "updated_at": "2026-11-09T14:03:11.204Z"
  }
}
POST/api/biz/orders/{ref}/return

Send held skins back to your user

For a sell in held (on_fail hold, or a return that failed).

Request body - optional
{
  "trade_url": null
}
Response
{
  "order": {
    "ref": "sale-1042",
    "id": 17,
    "side": "sell",
    "status": "returning",
    "user_ref": "u_381",
    "steamid": "76561198000000042",
    "items": [
      {
        "item_id": 2457,
        "name": "Hellforged Vest",
        "market_hash_name": "Hellforged Vest",
        "icon": "https://files.facepunch.com/rust/icons/inventory/rust/70010_small.png",
        "qty": 2
      }
    ],
    "min_net_mills": 1760,
    "received_mills": null,
    "fills": null,
    "trade_offer": {
      "id": 311,
      "status": "sent",
      "steam_offer_id": "7712345678",
      "url": "https://steamcommunity.com/tradeoffer/7712345678/",
      "expires_at": "2026-11-09T14:13:11.204Z",
      "reason": null
    },
    "reason": null,
    "created_at": "2026-11-09T14:03:11.204Z",
    "updated_at": "2026-11-09T14:03:11.204Z",
    "on_fail": "return",
    "return_offer": null
  }
}

Your users

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.

Request body
{
  "steamid": "76561198000000042",
  "trade_url": "https://steamcommunity.com/tradeoffer/new/?partner=39734314\u0026token=AbCd1234",
  "metadata": {
    "tier": "gold"
  }
}
Response
{
  "user": {
    "user_ref": "u_381",
    "steamid": "76561198000000042",
    "trade_url": "https://steamcommunity.com/tradeoffer/new/?partner=39734314\u0026token=AbCd1234",
    "name": "rustlord",
    "avatar": "https://avatars.steamstatic.com/\u2026_medium.jpg",
    "profile": {
      "persona": "rustlord",
      "profile_url": "https://steamcommunity.com/profiles/76561198000000042",
      "custom_url": null,
      "avatar": "https://avatars.steamstatic.com/\u2026_medium.jpg",
      "avatar_full": "https://avatars.steamstatic.com/\u2026_full.jpg",
      "avatar_small": "https://avatars.steamstatic.com/\u2026.jpg",
      "visibility": "public",
      "limited_account": false,
      "vac_banned": false,
      "trade_ban": "None",
      "member_since": "March 3, 2015",
      "location": null,
      "online_state": "offline"
    },
    "profile_fetched_at": "2026-11-09T14:03:11.204Z",
    "metadata": {
      "tier": "gold"
    },
    "connected_at": "2026-11-09T14:03:11.204Z",
    "created_at": "2026-11-09T14:03:11.204Z",
    "updated_at": "2026-11-09T14:03:11.204Z",
    "last_seen_at": "2026-11-09T14:03:11.204Z"
  },
  "created": true
}
GET/api/biz/users

Your users

QueryType
qstringSearch user_ref, Steam name, or an exact steamid.
before / afterintPaging.
limitint1-200.
Response
{
  "users": [
    {
      "user_ref": "u_381",
      "steamid": "76561198000000042",
      "trade_url": "https://steamcommunity.com/tradeoffer/new/?partner=39734314\u0026token=AbCd1234",
      "name": "rustlord",
      "avatar": "https://avatars.steamstatic.com/\u2026_medium.jpg",
      "profile": {
        "persona": "rustlord",
        "profile_url": "https://steamcommunity.com/profiles/76561198000000042",
        "custom_url": null,
        "avatar": "https://avatars.steamstatic.com/\u2026_medium.jpg",
        "avatar_full": "https://avatars.steamstatic.com/\u2026_full.jpg",
        "avatar_small": "https://avatars.steamstatic.com/\u2026.jpg",
        "visibility": "public",
        "limited_account": false,
        "vac_banned": false,
        "trade_ban": "None",
        "member_since": "March 3, 2015",
        "location": null,
        "online_state": "offline"
      },
      "profile_fetched_at": "2026-11-09T14:03:11.204Z",
      "metadata": {
        "tier": "gold"
      },
      "connected_at": "2026-11-09T14:03:11.204Z",
      "created_at": "2026-11-09T14:03:11.204Z",
      "updated_at": "2026-11-09T14:03:11.204Z",
      "last_seen_at": "2026-11-09T14:03:11.204Z"
    }
  ],
  "total": 1,
  "next": null
}
GET/api/biz/users/export

Export the directory

QueryType
formatstringjson (default) or csv.
Response
{
  "users": [
    {
      "user_ref": "u_381",
      "steamid": "76561198000000042",
      "trade_url": "https://steamcommunity.com/tradeoffer/new/?partner=39734314\u0026token=AbCd1234",
      "name": "rustlord",
      "avatar": "https://avatars.steamstatic.com/\u2026_medium.jpg",
      "profile": {
        "persona": "rustlord",
        "profile_url": "https://steamcommunity.com/profiles/76561198000000042",
        "custom_url": null,
        "avatar": "https://avatars.steamstatic.com/\u2026_medium.jpg",
        "avatar_full": "https://avatars.steamstatic.com/\u2026_full.jpg",
        "avatar_small": "https://avatars.steamstatic.com/\u2026.jpg",
        "visibility": "public",
        "limited_account": false,
        "vac_banned": false,
        "trade_ban": "None",
        "member_since": "March 3, 2015",
        "location": null,
        "online_state": "offline"
      },
      "profile_fetched_at": "2026-11-09T14:03:11.204Z",
      "metadata": {
        "tier": "gold"
      },
      "connected_at": "2026-11-09T14:03:11.204Z",
      "created_at": "2026-11-09T14:03:11.204Z",
      "updated_at": "2026-11-09T14:03:11.204Z",
      "last_seen_at": "2026-11-09T14:03:11.204Z"
    }
  ]
}
GET/api/biz/users/{user_ref}

One user

Response
{
  "user": {
    "user_ref": "u_381",
    "steamid": "76561198000000042",
    "trade_url": "https://steamcommunity.com/tradeoffer/new/?partner=39734314\u0026token=AbCd1234",
    "name": "rustlord",
    "avatar": "https://avatars.steamstatic.com/\u2026_medium.jpg",
    "profile": {
      "persona": "rustlord",
      "profile_url": "https://steamcommunity.com/profiles/76561198000000042",
      "custom_url": null,
      "avatar": "https://avatars.steamstatic.com/\u2026_medium.jpg",
      "avatar_full": "https://avatars.steamstatic.com/\u2026_full.jpg",
      "avatar_small": "https://avatars.steamstatic.com/\u2026.jpg",
      "visibility": "public",
      "limited_account": false,
      "vac_banned": false,
      "trade_ban": "None",
      "member_since": "March 3, 2015",
      "location": null,
      "online_state": "offline"
    },
    "profile_fetched_at": "2026-11-09T14:03:11.204Z",
    "metadata": {
      "tier": "gold"
    },
    "connected_at": "2026-11-09T14:03:11.204Z",
    "created_at": "2026-11-09T14:03:11.204Z",
    "updated_at": "2026-11-09T14:03:11.204Z",
    "last_seen_at": "2026-11-09T14:03:11.204Z"
  }
}
POST/api/biz/users/{user_ref}/refresh

Re-read their Steam profile now

At most once per 10 minutes per user (otherwise you get the stored one back).

Response
{
  "user": {
    "user_ref": "u_381",
    "steamid": "76561198000000042",
    "trade_url": "https://steamcommunity.com/tradeoffer/new/?partner=39734314\u0026token=AbCd1234",
    "name": "rustlord",
    "avatar": "https://avatars.steamstatic.com/\u2026_medium.jpg",
    "profile": {
      "persona": "rustlord",
      "profile_url": "https://steamcommunity.com/profiles/76561198000000042",
      "custom_url": null,
      "avatar": "https://avatars.steamstatic.com/\u2026_medium.jpg",
      "avatar_full": "https://avatars.steamstatic.com/\u2026_full.jpg",
      "avatar_small": "https://avatars.steamstatic.com/\u2026.jpg",
      "visibility": "public",
      "limited_account": false,
      "vac_banned": false,
      "trade_ban": "None",
      "member_since": "March 3, 2015",
      "location": null,
      "online_state": "offline"
    },
    "profile_fetched_at": "2026-11-09T14:03:11.204Z",
    "metadata": {
      "tier": "gold"
    },
    "connected_at": "2026-11-09T14:03:11.204Z",
    "created_at": "2026-11-09T14:03:11.204Z",
    "updated_at": "2026-11-09T14:03:11.204Z",
    "last_seen_at": "2026-11-09T14:03:11.204Z"
  }
}
GET/api/biz/users/{user_ref}/history

Everything a user did

Their orders and crypto, newest first, with totals: net_mills = sold + deposited - bought - withdrawn.

QueryType
limitint1-200 of each, default 100.
Response
{
  "user_ref": "u_381",
  "totals": {
    "sold_mills": 1782,
    "bought_mills": 941,
    "deposited_mills": 25000,
    "withdrawn_mills": 0,
    "net_mills": 25841
  },
  "orders": [
    {
      "ref": "sale-1042",
      "id": 17,
      "side": "sell",
      "status": "completed",
      "user_ref": "u_381",
      "steamid": "76561198000000042",
      "items": [
        {
          "item_id": 2457,
          "name": "Hellforged Vest",
          "market_hash_name": "Hellforged Vest",
          "icon": "https://files.facepunch.com/rust/icons/inventory/rust/70010_small.png",
          "qty": 2
        }
      ],
      "min_net_mills": 1760,
      "received_mills": 1782,
      "fills": [
        {
          "item_id": 2457,
          "name": "Hellforged Vest",
          "side": "sell",
          "qty": 2,
          "total": 1782,
          "avg_shown": 891
        }
      ],
      "trade_offer": {
        "id": 311,
        "status": "completed",
        "steam_offer_id": "7712345678",
        "url": "https://steamcommunity.com/tradeoffer/7712345678/",
        "expires_at": "2026-11-09T14:13:11.204Z",
        "reason": null
      },
      "reason": null,
      "created_at": "2026-11-09T14:03:11.204Z",
      "updated_at": "2026-11-09T14:03:11.204Z",
      "on_fail": "return",
      "return_offer": null
    }
  ],
  "crypto": [
    {
      "id": 904,
      "direction": "deposit",
      "user_ref": "u_381",
      "coin": "usdtsol",
      "status": "finished",
      "address": "7Xf3\u2026q9Z",
      "extra_id": null,
      "amount_crypto": null,
      "created_at": "2026-11-09T14:03:11.204Z",
      "updated_at": "2026-11-09T14:03:11.204Z",
      "credited_mills": 25000,
      "paid_crypto": "25.01"
    }
  ]
}
DELETE/api/biz/users/{user_ref}

Erase a user

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).

Request body
{
  "user_ref": "u_381",
  "return_url": "https://rustdrop.gg/account/steam-connected"
}
Response
{
  "url": "https://traderust.com/connect?b=b_3f9a1c2d7e4b\u0026user_ref=u_381\u0026return=https%3A%2F%2Frustdrop.gg%2F\u2026\u0026ts=1762697000\u0026sig=9c1e\u2026",
  "expires_at": "2026-11-09T14:33:20Z"
}

Crypto

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.

Response
{
  "coins": [
    {
      "coin": "usdtsol",
      "name": "USDT on Solana",
      "network_fee_mills": 260,
      "min_deposit_usd": 5.0
    }
  ],
  "withdraw_fee_percent": 2.0,
  "min_withdraw_mills": 5000
}
POST/api/biz/crypto/address

A deposit address for a user

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.

Request body
{
  "user_ref": "u_381",
  "coin": "usdtsol"
}
Response
{
  "address": {
    "user_ref": "u_381",
    "coin": "usdtsol",
    "address": "7Xf3\u2026q9Z",
    "extra_id": null,
    "min_deposit_usd": 5.0,
    "created_at": "2026-11-09T14:03:11.204Z"
  }
}
POST/api/biz/crypto/withdraw

Pay a user out in crypto

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
{
  "ref": "payout-77",
  "user_ref": "u_381",
  "coin": "usdtsol",
  "address": "9Qm1\u2026Lk2",
  "usd_mills": 50000
}
Response
{
  "withdrawal": {
    "id": 905,
    "direction": "withdraw",
    "user_ref": "u_381",
    "coin": "usdtsol",
    "status": "queued",
    "address": "9Qm1\u2026Lk2",
    "extra_id": null,
    "amount_crypto": null,
    "created_at": "2026-11-09T14:03:11.204Z",
    "updated_at": "2026-11-09T14:03:11.204Z",
    "ref": "payout-77",
    "debited_mills": 50000,
    "fee_mills": 1260,
    "payout_mills": 48740,
    "tx_hash": null
  },
  "created": true
}
GET/api/biz/crypto/withdrawals/{ref}

One payout

Response
{
  "withdrawal": {
    "id": 905,
    "direction": "withdraw",
    "user_ref": "u_381",
    "coin": "usdtsol",
    "status": "finished",
    "address": "9Qm1\u2026Lk2",
    "extra_id": null,
    "amount_crypto": null,
    "created_at": "2026-11-09T14:03:11.204Z",
    "updated_at": "2026-11-09T14:03:11.204Z",
    "ref": "payout-77",
    "debited_mills": 50000,
    "fee_mills": 1260,
    "payout_mills": 48740,
    "tx_hash": "4Nc2\u2026"
  }
}
GET/api/biz/crypto/transfers

Deposits and payouts

QueryType
directionstringdeposit or withdraw.
user_refstringOne user's.
before / afterintPaging.
limitint1-200.
Response
{
  "transfers": [
    {
      "id": 904,
      "direction": "deposit",
      "user_ref": "u_381",
      "coin": "usdtsol",
      "status": "finished",
      "address": "7Xf3\u2026q9Z",
      "extra_id": null,
      "amount_crypto": null,
      "created_at": "2026-11-09T14:03:11.204Z",
      "updated_at": "2026-11-09T14:03:11.204Z",
      "credited_mills": 25000,
      "paid_crypto": "25.01"
    },
    {
      "id": 905,
      "direction": "withdraw",
      "user_ref": "u_381",
      "coin": "usdtsol",
      "status": "queued",
      "address": "9Qm1\u2026Lk2",
      "extra_id": null,
      "amount_crypto": null,
      "created_at": "2026-11-09T14:03:11.204Z",
      "updated_at": "2026-11-09T14:03:11.204Z",
      "ref": "payout-77",
      "debited_mills": 50000,
      "fee_mills": 1260,
      "payout_mills": 48740,
      "tx_hash": null
    }
  ],
  "next": null
}

Events & webhooks

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.

QueryType
afterintEvents after this id, oldest first.
beforeintNewest first, before this id.
typestringComma-separated types.
limitint1-200, default 100.
Response
{
  "events": [
    {
      "id": 1201,
      "type": "sell.completed",
      "created_at": "2026-11-09T14:03:11.204Z",
      "data": {
        "ref": "sale-1042",
        "id": 17,
        "side": "sell",
        "status": "completed",
        "user_ref": "u_381",
        "steamid": "76561198000000042",
        "items": [
          {
            "item_id": 2457,
            "name": "Hellforged Vest",
            "market_hash_name": "Hellforged Vest",
            "icon": "https://files.facepunch.com/rust/icons/inventory/rust/70010_small.png",
            "qty": 2
          }
        ],
        "min_net_mills": 1760,
        "received_mills": 1782,
        "fills": [
          {
            "item_id": 2457,
            "name": "Hellforged Vest",
            "side": "sell",
            "qty": 2,
            "total": 1782,
            "avg_shown": 891
          }
        ],
        "trade_offer": {
          "id": 311,
          "status": "completed",
          "steam_offer_id": "7712345678",
          "url": "https://steamcommunity.com/tradeoffer/7712345678/",
          "expires_at": "2026-11-09T14:13:11.204Z",
          "reason": null
        },
        "reason": null,
        "created_at": "2026-11-09T14:03:11.204Z",
        "updated_at": "2026-11-09T14:03:11.204Z",
        "on_fail": "return",
        "return_offer": null
      },
      "delivery": {
        "delivered_at": "2026-11-09T14:03:11.204Z",
        "attempts": 1,
        "last_error": null,
        "next_attempt_at": null
      }
    }
  ],
  "next": null
}
POST/api/biz/webhook/test

Send a test event now

Queues a test.ping and delivers it immediately; the result is what your server answered.

Response
{
  "event_id": 1202,
  "ok": true,
  "status": 200,
  "error": null,
  "ms": 143
}

Errors

{"error": "The book pays $1.700 for this right now - less than min_net_mills.", "code": "price_changed", "quote": {…}}
codeHTTPMeaning
auth_required401No key.
bad_key401Wrong or revoked key.
scope403The key lacks the business scope.
business_required403The key/session isn't a business account's.
business_suspended403Suspended by TradeRust - contact support.
ip_not_allowed403Your IP isn't on the allowlist.
daily_limit403Over your daily limit (used_mills, limit_mills included).
insufficient_funds402Your balance can't pay for it.
price_changed409The book can't meet your min_net / max_cost right now. Nothing happened; a fresh quote is included.
ref_conflict409That ref is already used by an order of the other side.
user_incomplete409The user has no trade URL yet.
not_cancellable / not_retryable / not_returnable409Wrong state for that action.
user_not_found / not_found404No such user / order / payout.
invalid / invalid_steamid / unknown_item422The request is malformed; error says which field.
trade_url_invalid / trade_url_mismatch422Not a Steam trade URL, or it belongs to a different account.
return_url_not_allowed422Connect return URL isn't https on your domains.
unsupported_coin / payout_refused422Bad coin, address, or amount (under $5, or not enough left after fees).
rate_limited / quota_exceeded429Slow down (Retry-After).
inventory_unavailable502Steam wouldn't give us that inventory (private, or Steam is down).
crypto_unavailable / provider_error503The crypto provider is unavailable - try again shortly.

Client code

No SDK to install - the API is small. These are complete minimal clients:

import requests

class TradeRust:
    def __init__(self, key: str, base: str = "https://traderust.com"):
        self.base, self.s = base, requests.Session()
        self.s.headers["Authorization"] = f"Bearer {key}"

    def _call(self, method: str, path: str, **kw):
        r = self.s.request(method, self.base + path, timeout=20, **kw)
        data = r.json()
        if r.status_code >= 400:
            raise RuntimeError(f"{data['code']}: {data['error']}")
        return data

    def inventory(self, user_ref):        return self._call("GET", f"/api/biz/users/{user_ref}/inventory")
    def quote(self, side, items):         return self._call("POST", "/api/biz/quote", json={"side": side, "items": items})
    def sell(self, ref, user_ref, items, min_net_mills, on_fail="return"):
        return self._call("POST", "/api/biz/sell", json={"ref": ref, "user_ref": user_ref, "items": items,
                                                         "min_net_mills": min_net_mills, "on_fail": on_fail})["order"]
    def buy(self, ref, user_ref, items, max_cost_mills):
        return self._call("POST", "/api/biz/buy", json={"ref": ref, "user_ref": user_ref, "items": items,
                                                        "max_cost_mills": max_cost_mills})["order"]
    def put_user(self, user_ref, **fields): return self._call("PUT", f"/api/biz/users/{user_ref}", json=fields)["user"]
    def deposit_address(self, user_ref, coin): return self._call("POST", "/api/biz/crypto/address",
                                                                 json={"user_ref": user_ref, "coin": coin})["address"]
    def payout(self, ref, user_ref, coin, address, usd_mills):
        return self._call("POST", "/api/biz/crypto/withdraw", json={"ref": ref, "user_ref": user_ref, "coin": coin,
                                                                    "address": address, "usd_mills": usd_mills})["withdrawal"]

Going live checklist

  • 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-11First release: skins orders, user directory, Hosted Connect, crypto deposits and payouts, webhooks, sandbox.