Skip to content
SteamLabs API

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.

GET/api/v1/tradeups/contracts

Your trade-up contracts, newest first.

API key required
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
Bash
curl "https://dashboard.steamlabs.dev/api/v1/tradeups/contracts?status=completed&sort=profit&direction=desc" \
  -H "Authorization: Bearer $STEAMLABS_API_KEY"
JSON
{
    "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 }
}
GET/api/v1/tradeups/contracts/{id}

One contract, with its frozen input snapshot and full outcome odds.

API key required

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

GET/api/v1/tradeups/stats

Spend, realized value, net and hit rate across your completed contracts.

API key required

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.

GET/api/v1/tradeups/settings

Your saved trade-up scan settings.

API key required
PUT/api/v1/tradeups/settings

Replace your saved trade-up scan settings.

API key required
JSON
{
    "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:

GET/api/v1/tradeups/summary

How many contracts, slots and dollars the current scope and rails cover.

API key required

Every settings field can be sent as a query parameter here to preview a different rail without saving it.

POST/api/v1/tradeups/dry-run

Plan a clearing run and report the digest, creating nothing.

API key required
POST/api/v1/tradeups/clear-space

Stage every contract the scope yields under the current rails.

API key required
Bash
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}'
JSON
{
    "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

POST/api/v1/tradeups/scan

The best contract each account in scope could craft, ranked by expected return.

API key required

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.

JSON
{
    "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:

POST/api/v1/tradeups/queue-best-value

Stage the best contract one account can craft at one tier.

API key required

Requires tradeups.write and an Idempotency-Key header.

JSON
{
    "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.

GET/api/v1/tradeups/candidates

One account's trade-up eligible items, cheapest first.

API key required
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:

POST/api/v1/tradeups/craft

Stage one hand-picked contract from up to ten items.

API key required

Requires tradeups.write and an Idempotency-Key header.

JSON
{
    "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:

JSON
{
    "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
}