Endpoints
Trade-ups
Clear inventory clutter, hunt for value, and build contracts by hand, from code.
Trade-up contracts turn ten items of one grade into one of the next. The API gives you all three ways the dashboard does it: the bulk clearing run, the best-value scan, and the hand-built contract.
Two scopes cover the domain, split by verb rather than by effect. tradeups.read covers every GET. tradeups.write covers every POST and PUT, including the two that create nothing (dry-run and scan): a scope is a promise attached to a key that may live for a year, and what a handler does is free to change under it.
All money is integer cents, in USD.
The ledger
Every contract ever staged, with what it cost, the odds it was staged against, and what came out. Rows are kept forever, so this is the record to reconcile against.
/api/v1/tradeups/contractsYour trade-up contracts, newest first.
| Filter | Values |
|---|---|
account_id |
One of your Steam account IDs |
status |
pending, crafting, completed, failed, reconciled, cancelled |
rarity |
1-6 (Consumer through Covert) |
is_stattrak |
true / false |
search |
Matches the account username |
sort |
created_at, input_cost, planned_ev, realized_value, profit |
direction |
asc / desc |
curl "https://dashboard.steamlabs.dev/api/v1/tradeups/contracts?status=completed&sort=profit&direction=desc" \
-H "Authorization: Bearer $STEAMLABS_API_KEY"{
"data": [
{
"id": "019fb42e-9a61-70d2-818a-f6a56593f3a5",
"steam_account": { "id": "019fb42e-9a7e-728d-b960-8b4c2162898c", "username": "farm_014" },
"status": "completed",
"rarity": 1,
"rarity_label": "Consumer",
"is_stattrak": false,
"input_count": 10,
"input_collections": 3,
"input_cost_cents": 350,
"planned_ev_cents": 420,
"realized_value_cents": 505,
"profit_cents": 155,
"output": {
"market_hash_name": "MP9 | Slide (Field-Tested)",
"paint_wear": 0.2074
},
"created_at": "2026-07-30T09:14:22+00:00"
}
],
"meta": { "page": 1, "per_page": 50, "total": 1, "last_page": 1 }
}/api/v1/tradeups/contracts/{id}One contract, with its frozen input snapshot and full outcome odds.
The detail view adds inputs (the ten assets consumed, with the price each carried at craft time) and planned_outcomes (every possible result with its probability, predicted float and price).
/api/v1/tradeups/statsSpend, realized value, net and hit rate across your completed contracts.
Settings
The scan settings the clearing run uses when you do not override them: which accounts, the price rail, how many contracts per account, which tiers, and whether StatTrak items may be burned. Shared with the dashboard, so changing them here changes what the Trade-Ups page does.
/api/v1/tradeups/settingsYour saved trade-up scan settings.
/api/v1/tradeups/settingsReplace your saved trade-up scan settings.
{
"account_ids": ["019fb42e-9a7e-728d-b960-8b4c2162898c"],
"max_item_price_cents": 10,
"max_contracts": 25,
"rarities": [1, 2],
"stattrak": "any"
}stattrak is any, normal (never burn StatTrak) or stattrak (only StatTrak). rarities accepts 1-5; Covert has no clearing contract. A PUT replaces the whole object, so anything you leave out goes back to its default.
Instead of account_ids you can send a filters object, which is resolved server-side. Accepted keys: search, guard, wallet, details, market, csfloat, inventory, session, tags, wallet_currency, wallet_min, wallet_max. A bad value for one of those is a 422 naming the key.
Clearing space
What the current scope would free, without touching anything:
/api/v1/tradeups/summaryHow many contracts, slots and dollars the current scope and rails cover.
Every settings field can be sent as a query parameter here to preview a different rail without saving it.
/api/v1/tradeups/dry-runPlan a clearing run and report the digest, creating nothing.
/api/v1/tradeups/clear-spaceStage every contract the scope yields under the current rails.
curl -X POST https://dashboard.steamlabs.dev/api/v1/tradeups/clear-space \
-H "Authorization: Bearer $STEAMLABS_API_KEY" \
-H "Idempotency-Key: 7f3a9c2e-1b44-4d8a-9f01-cc2e5a9b1234" \
-H "Content-Type: application/json" \
-d '{"rarities": [1, 2], "max_item_price_cents": 10}'{
"task_id": "019fb431-2c88-71ea-b0a3-8ce2f5d19b07",
"accounts_affected": 42,
"accounts_skipped": 3,
"async": true
}An empty rarities array is refused with 422 empty_rarity_scope rather than falling back to a default: asking for no tiers must never burn items in tiers you did not name.
If no account in scope holds enough eligible items you get 422 no_contracts_available. That is a different answer from 403 plan_limit_reached, which means your plan does not sell trade-ups at all. Both differ from 503 maintenance_mode, which is transient: contract creation is paused during platform maintenance and the same call succeeds once it ends.
Hunting value
/api/v1/tradeups/scanThe best contract each account in scope could craft, ranked by expected return.
The scan looks at the first 25 accounts of the scope and returns the top ten proposals, with capped: true when there were more. It creates nothing and needs no Idempotency-Key, but it is a POST and so still requires tradeups.write.
{
"plans": [
{
"account": { "id": "019fb42e-9a7e-728d-b960-8b4c2162898c", "username": "farm_014" },
"rarity": 2,
"rarity_label": "Industrial",
"input_cost_cents": 210,
"expected_value_cents": 285,
"expected_return": 0.357
}
],
"accounts_scanned": 25,
"capped": true,
"account_cap": 25
}Queue one of them:
/api/v1/tradeups/queue-best-valueStage the best contract one account can craft at one tier.
Requires tradeups.write and an Idempotency-Key header.
{
"account_id": "019fb42e-9a7e-728d-b960-8b4c2162898c",
"rarity": 2
}rarity is required and accepts 1 to 5. stattrak is optional and falls back to your saved policy. An account that already has a trade-up task queued or running answers 409 account_busy, and an account id that is not yours answers 404 account_not_found.
Building a contract by hand
The candidate pool is every item on one account that could legally be an input right now: unreserved, tradable, not in a storage unit, and part of a collection with an outcome pool.
/api/v1/tradeups/candidatesOne account's trade-up eligible items, cheapest first.
| Parameter | Values |
|---|---|
account_id |
Required |
rarity |
1-6; omit for every craftable tier |
stattrak |
any (default), normal, stattrak |
collections |
Collection IDs to keep |
float_min, float_max |
0-1 |
sort |
price (default) or float |
Then craft:
/api/v1/tradeups/craftStage one hand-picked contract from up to ten items.
Requires tradeups.write and an Idempotency-Key header.
{
"account_id": "019fb42e-9a7e-728d-b960-8b4c2162898c",
"item_ids": ["019fb42e-9c10-7233-8f7a-5b2d90c14a77", "019fb42e-9c22-70ab-b3c4-118e6f2d0e59"]
}item_ids takes 1 to 10 ids and they must be distinct: the same row twice is one item pretending to be two.
A contract takes exactly ten inputs (five for a Covert knife/glove contract), and all of them must share one rarity and one StatTrak state. Break either rule and the answer names which:
| Code | Meaning |
|---|---|
mixed_rarity |
The items span more than one grade |
mixed_stattrak |
Some are StatTrak and some are not |
incomplete_contract |
Wrong number of usable inputs; required, usable and submitted say how far off |
inputs_not_found |
None of those IDs are on that account |
no_next_tier |
That grade has no contract |
unknown_skins |
An item is not a recognised skin, so its odds cannot be computed |
account_busy |
That account already has a trade-up task queued or running |
inputs_unavailable |
Another contract reserved one of those items first; refetch the candidates |
A successful craft returns 202 with the task and the ledger row it created:
{
"task_id": "019fb431-2c88-71ea-b0a3-8ce2f5d19b07",
"accounts_affected": 1,
"contract_ids": ["019fb431-2c9a-73bb-9f0e-7d51c3a44f18"],
"input_cost_cents": 342,
"expected_value_cents": 410
}