Listing API
Your listed bot tells the bot list its own rates and stock, so the list always shows what it has right now. Free for every listed bot.
This is the free Listing API: it only updates your listing, with keys starting stx_. It does not read inventories. Reading Steam inventories for your bot is the paid Inventory API, a different thing with different keys (inv_): see the Inventory API reference.
Introduction
Every bot on the list shows its rate, its stock and the highest level it can take a customer to. With this API your bot sends those itself, every few minutes, instead of you typing them in.
- It costs nothing. It works for any bot that is live on the list.
- A bot that has not reported for 8 hours moves to the Missing Data table until it reports again.
- Advertising a better rate than our rate checker measured takes the Rate checked mark off your listing until it is checked again. Reporting the same or a worse rate leaves it alone.
Running the levelup-bot script? It cannot send these updates by itself. Run the free SteamTradeX Reporter next to it: no coding, about 10 minutes. New to this? The step by step Listing API guide walks through it.
Authentication
Send your Listing API key as a Bearer token:
Authorization: Bearer stx_your_listing_key
- Get it in Listed Bots, Edit Bot, under Listing API key: press Issue a Listing API Key. It is shown once, so copy it then.
- Each key belongs to one listing and can only change that bot’s rates and stock. Issuing a new one stops the old one at once, which is also how you rotate it if it leaks.
- Instead of the header, you may put the key in the body as
secret(see Other field names). The header is read first.
Quick start
- Make sure your bot is live on the list, then issue its key in Listed Bots, Edit Bot.
- Send what your bot has now:
curl -X POST https://steamtradex.com/api/v1/bots/update \
-H "Authorization: Bearer stx_your_listing_key" \
-H "Content-Type: application/json" \
-d '{"sets_in_stock":5000,"games_in_stock":300,"max_level":250,"buy_rate":18.5,"sell_rate":21,"buy_gems_per_set":445,"sell_gems_per_set":300}'The answer is {"ok":true,"updated_at":"2026-10-07T12:00:00.000Z"}, and your listing shows the new numbers. Send again every 5 minutes and after each trade.
Endpoint
- Body: a JSON object, with
Content-Type: application/json. Up to 2 MB. - Send only what you have. Fields you leave out keep their last value, so a bot that only knows its stock can send just that.
- Numbers may be JSON numbers or strings of plain digits (
18.5or"18.5"). Nothing else is read as a number: not1e3, not0x10, not spaces.
Fields
| Field | Type and range | What it is |
|---|---|---|
| sets_in_stock | Whole number, 0 to 100,000,000 | Total card sets the bot holds. |
| games_in_stock | Whole number, 0 to 50,000 | How many games those sets are from. Not enough on its own: send it with something else. |
| max_level | Whole number, 0 to 6,000 | The Steam level a level 0 account reaches by crafting every normal set the bot holds, counting at most 5 sets per game. Foil and event sets do not count. 0 means the bot has nothing to sell. See How max_level is worked out. |
| buy_rate | Number, 1 to 500 | Sets a customer gets per TF2 key when buying from the bot. Shown as the Rate, such as 18.5:1. Kept to two decimal places. |
| sell_rate | Number, 1 to 500 | Sets a customer gives per TF2 key when selling to the bot. |
| buy_gems_per_set | Whole number, 10 to 100,000 | Whole gems a customer pays for one set when buying with gems. The larger of the two gem numbers. |
| sell_gems_per_set | Whole number, 10 to 100,000 | Whole gems a customer is paid for one set when selling to the bot. |
| bot_id | SteamID64 | Optional. Your bot's SteamID64. When sent, it must be the bot this key belongs to, or the update is refused with 403. |
Every field is optional, but at least one of them (other than games_in_stock and bot_id) must be there. Rates are sets per TF2 key and are “from the customer’s side”: buy_rate is what a customer gets when buying. Gems are the other way up: whole gems for one set.
How max_level is worked out
Send the level that a brand new account, at level 0, would reach by crafting every normal set your bot holds. A Steam badge levels up at most 5 times per game, so count no more than 5 sets for any one game. Foil and event sets do not count. Each set crafted is 100 XP, and Steam’s level table turns the XP into a level.
For example, a bot holding 8 sets of Game A, 2 sets of Game B and 5 sets of Game C counts 5 + 2 + 5 = 12 sets, which is 1200 XP from level 0: max_level 11. A bot with no normal sets left sends 0, which means it has nothing to sell.
If your bot will not level anyone past a set level, send the lower of the two. The Reporter works this out for you, capped by its MAX_LEVEL setting. A bot that never reports it may get an estimate from our rate checker, which a figure you send always replaces.
A full update
{
"sets_in_stock": 5000,
"games_in_stock": 300,
"max_level": 250,
"buy_rate": 18.5,
"sell_rate": 21,
"buy_gems_per_set": 445,
"sell_gems_per_set": 300
}Other field names
Already report to another bot list? We accept its field names too, so a script that reports there needs only our address and your SteamTradeX key:
| Field | Read as |
|---|---|
| secret | Your Listing API key, in the body instead of the Authorization header. Only read when there is no Bearer header. |
| stockData | An object of app id to sets for that game, such as { "440": 10, "570": 15 }. We add it up into sets_in_stock and games_in_stock (games with 0 sets are not counted), and it replaces both if you send them too. Up to 50,000 games, 10,000,000 sets a game, 100,000,000 in total. |
| tf2_buy_rate | Our buy_rate. Sets a customer gets per TF2 key when buying from the bot. Shown as the Rate, such as 18.5:1. Kept to two decimal places. |
| tf2_sell_rate | Our sell_rate. Sets a customer gives per TF2 key when selling to the bot. |
| gems_sell_rate | Our buy_gems_per_set. Whole gems a customer pays for one set when buying with gems. The larger of the two gem numbers. |
| gems_buy_rate | Our sell_gems_per_set. Whole gems a customer is paid for one set when selling to the bot. |
Gems in these names are read from the bot’s side: gems_sell_rate is what your bot sells a set for (what a customer pays, the larger number) and gems_buy_rate is what it pays for one. If your numbers show the wrong way round on the list, send ours instead. When a body has both names for the same thing, ours wins.
POST https://steamtradex.com/api/v1/bots/update
Content-Type: application/json
{
"secret": "stx_your_listing_key",
"bot_id": "76561198012345678",
"stockData": {
"440": 10,
"570": 15
},
"tf2_buy_rate": 8.1,
"tf2_sell_rate": 9.5,
"gems_sell_rate": 445,
"gems_buy_rate": 300
}Response
Every answer is JSON with ok. Saved:
{
"ok": true,
"updated_at": "2026-10-07T12:00:00.000Z"
}updated_at is when it was saved, in UTC. Refused, with a sentence your script can log:
{
"ok": false,
"error": "too soon: send at most one update every 3 seconds"
}Errors
A refused update changes nothing. Every error the endpoint can send, word for word:
| Status | error | When |
|---|---|---|
| 400 | send a JSON body | Content-Type is not application/json, or the body is not a JSON object. |
| 400 | the body is too large: 2 MB at most | The body is over 2 MB. |
| 400 | bot_id must be a SteamID64 | bot_id is not 17 digits starting 7656119. |
| 400 | stockData must be an object of appid to sets | stockData is not an object. |
| 400 | stockData is too large | More than 50,000 games in stockData. |
| 400 | stockData keys must be app ids | A key in stockData is not digits. |
| 400 | stockData values must be whole numbers | A value in stockData is not a whole number from 0 to 10,000,000. |
| 400 | stockData adds up to more than 100,000,000 sets | The games add up to too many sets. |
| 400 | sets_in_stock must be a whole number, 100,000,000 at most | sets_in_stock is out of range or not a whole number. |
| 400 | games_in_stock must be a whole number, 50000 at most | games_in_stock is out of range or not a whole number. |
| 400 | max_level must be a whole number, 6000 at most | max_level is out of range or not a whole number. |
| 400 | buy_rate must be sets per key, from 1 to 500 | buy_rate (or tf2_buy_rate) is out of range. |
| 400 | sell_rate must be sets per key, from 1 to 500 | sell_rate (or tf2_sell_rate) is out of range. |
| 400 | buy_gems_per_set must be whole gems per set, from 10 to 100000 | buy_gems_per_set (or gems_sell_rate) is out of range or not whole. |
| 400 | sell_gems_per_set must be whole gems per set, from 10 to 100000 | sell_gems_per_set (or gems_buy_rate) is out of range or not whole. |
| 400 | nothing to update: send stock or rates | None of the fields was sent (games_in_stock alone does not count). |
| 400 | a number in the report is out of range | A number got past the checks above but is still too big to store. |
| 401 | missing or bad key | No key, a wrong key, or the bot is not live on the list. All three get the same answer. |
| 403 | this key belongs to a different bot | bot_id is not the bot this key was issued for. |
| 429 | too soon: send at most one update every 3 seconds | Less than 3 seconds since this bot's last saved update. |
| 500 | we could not save that just now: try again in a minute | Our database had trouble. Nothing was saved. |
A wrong key, no key and a bot that is not live all get the same 401, so a key cannot be used to find out which listing it belongs to.
Limits
- One saved update every 3 seconds per bot. Sooner gets
429and is not saved. Every 5 minutes, and after each trade, is plenty. - Bodies up to 2 MB, which is far more than any real report.
- Only bots that are live on the list. A bot that is not (still waiting for review, for example) gets
401.
Card sets
How many cards make a set, per game, to help a reporter turn cards into sets. Public: no key needed to read what we know.
{
"440": 9,
"570": 8,
"730": 5
}appids: comma separated app ids, up to 500 at once. Leave it out to get every game we know.- A game missing from the answer is not known yet. Send your Listing API key as
Authorization: Bearerand we look up the games we do not know, up to 1,200 new games a key an hour. Ask again a little later. - A key that is wrong gets
401with{"error":"missing or bad key"}, so a reporter with an old key finds out. A badappidsgets400.
Code examples
Keep the key in the STEAMTRADEX_LISTING_KEY environment variable rather than in the code.
curl -X POST https://steamtradex.com/api/v1/bots/update \
-H "Authorization: Bearer stx_your_listing_key" \
-H "Content-Type: application/json" \
-d '{"sets_in_stock":5000,"games_in_stock":300,"max_level":250,"buy_rate":18.5,"sell_rate":21,"buy_gems_per_set":445,"sell_gems_per_set":300}'// Node 18 or newer. Run every 5 minutes and after each trade.
const KEY = process.env.STEAMTRADEX_LISTING_KEY; // stx_...
async function report(stats) {
const res = await fetch("https://steamtradex.com/api/v1/bots/update", {
method: "POST",
headers: { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json" },
body: JSON.stringify(stats),
});
const body = await res.json();
if (!body.ok) console.error("Listing update refused:", res.status, body.error);
return body;
}
report({
"sets_in_stock": 5000,
"games_in_stock": 300,
"max_level": 250,
"buy_rate": 18.5,
"sell_rate": 21,
"buy_gems_per_set": 445,
"sell_gems_per_set": 300
});# pip install requests
import os, requests
KEY = os.environ["STEAMTRADEX_LISTING_KEY"] # stx_...
def report(stats):
r = requests.post("https://steamtradex.com/api/v1/bots/update",
headers={"Authorization": f"Bearer {KEY}"},
json=stats, timeout=30)
body = r.json()
if not body.get("ok"):
print("Listing update refused:", r.status_code, body.get("error"))
return body
report({"sets_in_stock": 5000, "games_in_stock": 300, "buy_rate": 18.5})<?php
$key = getenv('STEAMTRADEX_LISTING_KEY'); // stx_...
$ch = curl_init('https://steamtradex.com/api/v1/bots/update');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer $key", 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode(['sets_in_stock' => 5000, 'buy_rate' => 18.5]),
]);
$body = json_decode(curl_exec($ch), true);
if (empty($body['ok'])) echo 'Refused: ' . $body['error'] . "\n";OpenAPI
The Listing API as an OpenAPI 3.1 file, built from the same limits as this page. Import it into Postman, Insomnia or Bruno, or feed it to a code generator.
https://steamtradex.com/docs/listing-api/openapi.json