Introduction
Let your bot keep its own rates and stock up to date on SteamTradeIndex, or read the directory from your own tools. Updating your bot is free, you only need an API key.
Base URL: https://steamtradeindex.com/api/v1/{apiKey}
How it works
Two separate things are easy to mix up:
1. Your listing
Keeping your bot visible in the directory starts with a free 14 day trial, then 1 TF2 key keeps it listed for 10 days (see your bot's dashboard page). It has nothing to do with the API.
2. Your API key, free
Lets your bot software push rates and stock. No fee, no limits beyond the rate limits below. Create one for each bot from API keys.
Quick start
- Open Dashboard → API keys, choose your bot under Key for and click Generate key. You can view and copy it there any time.
- Put the key into your bot software, or use the ready-made sender in Connect your own bot, so it sends your rates.
- Check your bot's page on the site, the rates and a fresh "updated" time should appear.
curl -X POST "https://steamtradeindex.com/api/v1/YOUR_API_KEY/updateBot" \
-H "Content-Type: application/json" \
-d '{ "tf2SetsPerKey": 6, "gemBuyPrice": 900, "gemSellPrice": 300, "botStatus": "online" }'Gem or crypto bot?
gemStock and its key rates to /updateGemBot, a crypto bot sends buyPriceUsd, itemStock and its coins to /updateCryptoBot. Or send either to /push and the site works it out. A request that contains no field the bot's kind uses is refused with a 400 that lists the accepted fields, so a wrong field name never looks like a success.Send this to your bot developer
Paste it to your bot developer, or to an AI assistant that has your bot's code. It already knows the address, the field names and how often to push. Add your API key to the bot's .env file yourself, it is not included here.
I run a Steam trading bot that is listed on SteamTradeIndex (https://steamtradeindex.com). Please make my bot software push its live rates to SteamTradeIndex automatically, so my listing always shows real numbers.
WHAT TO BUILD
A small job inside the bot (or a separate script next to it) that sends one HTTP POST with JSON to SteamTradeIndex at startup, then right away whenever a rate or the stock changes (check for changes about once a minute and send only the numbers that changed, never more than one push a minute), plus a heartbeat on a random gap of 25 to 55 minutes each time when nothing changed (so many bots do not all call in together). If a push fails, retry after 2 minutes, then 5, then every 10 until it succeeds. SteamTradeIndex hides a bot that has been silent for 2 hours.
THE API KEY
- I will give you a SteamTradeIndex API key. It looks like bdx_live_xxxxxxxx. It belongs to this one bot, so no bot id is needed.
- Read it from an environment variable named STEAMTRADEINDEX_API_KEY in the bot's .env file. Never hard-code it, never commit it, never print it in logs.
REQUEST
POST https://steamtradeindex.com/api/v1/bot/update.php
Header: Authorization: Bearer <the key>
Header: Content-Type: application/json
Body: one flat JSON object. Build it from the bot's own settings so I never type a rate twice.
Card / holiday bot fields (all optional, a field that is left out stays unchanged, 0 or null clears a rate):
- buy_sets_tf2: card sets a trader gets per TF2 key (the bot's RATES_TF2_SELL)
- sell_sets_tf2: sets the bot takes per TF2 key it pays (RATES_TF2_BUY)
- buy_sets_tod / sell_sets_tod: the same for Tour of Duty tickets (RATES_TOD_SELL / RATES_TOD_BUY)
- buy_sets_csgo / sell_sets_csgo, buy_sets_hydra / sell_sets_hydra: the same for CS2 and Hydra keys, only if the bot trades them
- buy_gems_set: gems a trader pays for one set (RATES_GEMS_SELL)
- sell_gems_set: gems the bot pays for one set (RATES_GEMS_BUY)
- stock_sets: complete sets in stock (optional, if left out SteamTradeIndex reads the public Steam inventory itself)
- stock_badges: optional object of sets per game, for example { "730": 15, "570": 42 }
- max_level: optional, the highest level the bot can take a customer to
- status: "online", "busy", "offline" or "maintenance"
Example body:
{ "status": "online", "buy_sets_tf2": 6, "sell_sets_tf2": 7, "buy_gems_set": 900, "sell_gems_set": 300, "stock_sets": 1060 }
RESPONSE
- 200 with { "success": true, "message": "Bot data updated." } means it worked.
- 401 means the key is wrong or was revoked: log a clear error and stop retrying until the key changes.
- 400 means a field has the wrong type or is out of range: log the message, it names the field.
- 429 means too many requests: wait at least a minute, then continue. Do not send more than a few requests per minute.
- Any network error or 5xx: retry later with backoff, never crash the bot because of it.
RULES
- The job must never block trading. Run it in the background and swallow errors after logging them.
- Send honest numbers only. SteamTradeIndex shows a bot as stale when it has not pushed for a while and hides it after 2 hours of silence.
- When the bot is shutting down for maintenance, send { "status": "maintenance" } first. Send { "status": "online" } when it is back.
- Stock is optional. Send it only if the bot really knows its numbers. SteamTradeIndex checks card stock against the bot's public Steam inventory every hour: the same or fewer sets than the inventory holds is fine, clearly more makes the site ignore the pushed stock until it agrees. If stock is left out, SteamTradeIndex reads the inventory itself. Keep the Steam inventory public.
TEST IT
After you implement it, test with:
curl -X POST "https://steamtradeindex.com/api/v1/bot/update.php" -H "Authorization: Bearer YOUR_KEY" -H "Content-Type: application/json" -d '{ "status": "online", "buy_sets_tf2": 6, "sell_sets_tf2": 7, "buy_gems_set": 900, "sell_gems_set": 300, "stock_sets": 1060 }'
Then open my bot page on https://steamtradeindex.com and check that the rates and a fresh "updated" time appear. Full docs: https://steamtradeindex.com/api-docs
Tell me which file you changed and which line in .env I need to fill in (STEAMTRADEINDEX_API_KEY).Already reporting to another catalog?
{ selling, buying, sets }. That body is accepted here as is, at https://steamtradeindex.com/api/v1/{apiKey}/updateBot. Older versions that send a GET with tf2rate, gemrate, … to /update work too. In both cases the bot only needs a key from this site and its catalog address pointed here, no other code changes.One address for any bot
POST https://steamtradeindex.com/api/v1/{apiKey}/push works for card, gem and crypto bots. Paste your key and send whatever your bot software already sends: the site recognises the format. See /push. The same works with the key in a header at /bot/update.php.Connect your own bot
This works for any bot in any language. Your bot sends its current numbers to one address, whenever they change. If an admin changes a rate with a chat command, the new rate is on the site seconds later, and nobody edits a file.
- Create a key for your bot on API keys. A key made for one bot always updates that bot, no bot id needed.
- Save it in your bot's settings as
STEAMTRADEINDEX_API_KEY. Read it from there, never write it into the code and never print it in logs. - Add the small function below to your bot. Call it when the bot starts, right after any rate or stock changes (for example in the code that handles your rate command) and every 25 to 55 minutes (a random gap each time, so many bots never call in together), so the listing never goes stale. Between heartbeats, send only when a rate or the stock really changed, and at most once a minute. If a push fails, retry after 2 minutes, then 5, then every 10 until it works.
// Node 18 or newer, nothing to install. Put this anywhere in your bot.
async function pushToSteamTradeIndex(fields) {
const key = process.env.STEAMTRADEINDEX_API_KEY;
if (!key) return; // not set up: do nothing
try {
const res = await fetch("https://steamtradeindex.com/api/v1/bot/update.php", {
method: "POST",
headers: {
Authorization: "Bearer " + key,
"Content-Type": "application/json",
"User-Agent": "my-bot/1.0",
},
body: JSON.stringify({ status: "online", ...fields }),
signal: AbortSignal.timeout(15000),
});
const data = await res.json().catch(() => ({}));
if (!res.ok) console.error("SteamTradeIndex:", res.status, data.message || "");
} catch (err) {
console.error("SteamTradeIndex: could not reach the site:", err.message); // never log the key
}
}
// Call it with your bot's CURRENT numbers, for example a gem bot:
pushToSteamTradeIndex({
buy_gems_key_tf2: 3200, // gems your bot pays for one TF2 key
sell_gems_key_tf2: 3700, // gems your bot asks for one TF2 key
stock_gems: 25996, // gems in stock
});# Python 3, nothing to install. Put this anywhere in your bot.
import json, os, urllib.request, urllib.error
def push_to_steamtradeindex(fields):
key = os.environ.get("STEAMTRADEINDEX_API_KEY")
if not key:
return # not set up: do nothing
body = json.dumps({"status": "online", **fields}).encode()
req = urllib.request.Request(
"https://steamtradeindex.com/api/v1/bot/update.php",
data=body,
method="POST",
headers={
"Authorization": "Bearer " + key,
"Content-Type": "application/json",
"User-Agent": "my-bot/1.0",
},
)
try:
urllib.request.urlopen(req, timeout=15).read()
except urllib.error.HTTPError as e:
print("SteamTradeIndex:", e.code, e.read().decode()[:200])
except Exception as e:
print("SteamTradeIndex: could not reach the site:", e) # never print the key
# Call it with your bot's CURRENT numbers, for example a gem bot:
push_to_steamtradeindex({
"buy_gems_key_tf2": 3200, # gems your bot pays for one TF2 key
"sell_gems_key_tf2": 3700, # gems your bot asks for one TF2 key
"stock_gems": 25996, # gems in stock
})Which numbers do I send?
buy_* is what your bot gives for one key or ticket, sell_* is what it asks. For a gem bot that paying 3200 gems for a TF2 key and asking 3700 is buy_gems_key_tf2: 3200 and sell_gems_key_tf2: 3700. Card bots and crypto bots have their own fields, see the tables under /bot/update.php. Send only what your bot really trades. A field you leave out keeps its last value, and 0 or null clears it.Check that it worked
{ "success": true, "message": "Bot data updated." }. A 401 means the key is wrong or was regenerated, a 400 names the field with a bad value, a 404 means the bot is not created on the dashboard yet. Then open your bot's page: the rates and a fresh "updated" time should be there.No coding? A ready-made sender for fixed rates
If your rates rarely change, this small program reads them from your .env and sends them every right away when a number changes, plus a heartbeat every random 25 to 55 minutes (retrying sooner after a failure). Every STI_ line is one field from the /bot/update.php table in capitals, for example STI_BUY_GEMS_KEY_TF2=3200. It needs Node 18 or newer. Save it as steamtradeindex-push.mjs next to your .env and run node steamtradeindex-push.mjs.
STEAMTRADEINDEX_API_KEY=paste_your_key_here
STI_BUY_GEMS_KEY_TF2=3200
STI_SELL_GEMS_KEY_TF2=3700
STI_STOCK_GEMS=2550000// steamtradeindex-push.mjs
// Keeps your SteamTradeIndex listing up to date. Needs Node 18 or newer, nothing to install.
// Put it next to your bot's .env file and run: node steamtradeindex-push.mjs
import { readFileSync } from "node:fs";
const SITE = "https://steamtradeindex.com";
// A number that changed is sent right away (checked every minute). When nothing changes, a
// heartbeat is still sent after a random 25 to 55 minutes, so many bots never call in at the
// same second. The site hides a bot after 2 hours of silence, so keep the longest gap well below that.
const CHECK_SECONDS = 60;
const MIN_MINUTES = 25;
const MAX_MINUTES = 55;
// After a failed push, try again sooner: 2, 5, then every 10 minutes until it works.
const RETRY_MINUTES = [2, 5, 10];
// Reads .env again on every push, so you can edit a rate and it is picked up
// at the next push without restarting. Text after " #" on a line is a comment.
function readEnv() {
const env = { ...process.env };
try {
for (const line of readFileSync(".env", "utf8").split(/\r?\n/)) {
const m = line.match(/^\s*([A-Za-z0-9_]+)\s*=\s*(.*?)\s*$/);
if (m && !(m[1] in process.env)) env[m[1]] = m[2].replace(/\s+#.*$/, "").replace(/^["']|["']$/g, "");
}
} catch {}
return env;
}
// Every STI_ line in .env becomes one field of the update:
// STI_STOCK_GEMS=25996 -> "stock_gems": 25996
function fromEnv(env) {
const body = {};
for (const [name, value] of Object.entries(env)) {
if (!name.startsWith("STI_") || value === "") continue;
const field = name.slice(4).toLowerCase();
if (field === "status") body.status = value.toLowerCase();
else if (Number.isFinite(Number(value))) body[field] = Number(value);
}
return body;
}
// Optional: take numbers straight from your own bot instead of .env, e.g.
// return { stock_gems: myBot.gemBalance(), buy_gems_key_tf2: myBot.rates.tf2 };
async function live() {
return {};
}
async function push() {
const env = readEnv();
const key = env.STEAMTRADEINDEX_API_KEY;
if (!key) {
console.error("STEAMTRADEINDEX_API_KEY is missing in .env");
return false;
}
const body = { status: "online", ...fromEnv(env), ...(await live()) };
try {
const res = await fetch(SITE + "/api/v1/bot/update.php", {
method: "POST",
headers: { Authorization: "Bearer " + key, "Content-Type": "application/json" },
body: JSON.stringify(body),
});
const data = await res.json().catch(() => ({}));
console.log(new Date().toISOString(), res.status, data.message || "");
// 5xx and 429 are worth retrying; a 4xx (wrong key, bad value) needs a fix first, but keep trying on the normal schedule.
return res.ok;
} catch (err) {
console.error("Could not reach SteamTradeIndex, will retry:", err.message);
return false;
}
}
let failures = 0;
let lastSent = ""; // the numbers of the last successful push
let nextBeat = 0; // when the next heartbeat or retry is due (ms)
let busy = false;
function heartbeatGap() {
return (MIN_MINUTES + Math.random() * (MAX_MINUTES - MIN_MINUTES)) * 60 * 1000;
}
async function tick() {
if (busy) return;
busy = true;
try {
const key = readEnv().STEAMTRADEINDEX_API_KEY;
const current = JSON.stringify({ ...fromEnv(readEnv()), ...(await live()) });
const changed = key && current !== lastSent;
// After a failure, wait for the retry time even though the numbers still differ.
if (Date.now() < nextBeat && (failures > 0 || !changed)) return;
const ok = await push();
failures = ok ? 0 : failures + 1;
if (ok) lastSent = current;
nextBeat = Date.now() + (ok ? heartbeatGap() : RETRY_MINUTES[Math.min(failures, RETRY_MINUTES.length) - 1] * 60 * 1000);
} finally {
busy = false;
}
}
tick();
setInterval(tick, CHECK_SECONDS * 1000);
Authentication
On /bot/update.php the key goes in a header, Authorization: Bearer <key>, which keeps it out of server logs. On /push and the other endpoints it goes in the URL path:
https://steamtradeindex.com/api/v1/bdx_live_xxxxxxxx/getBots- A key made for one bot always updates that bot, no bot id is ever needed.
- An account-wide key ("All my bots") updates your only matching bot automatically; with several, add
botIdto the request body. - Keep keys private. Anyone with a key can change that bot's listing. If one leaks, press Regenerate on the API keys page.
/api/v1/{apiKey}/push
FREEOne address for any bot software. The site recognises the format and stores it.
Paste your API key into the bot software (or into this address) and let it send what it already sends. We look at the field names, work out which kind of bot is talking and apply the data to the right listing. A key made for one bot always updates that bot. With an account-wide key, the format picks the bot, as long as exactly one of your bots fits; otherwise add botId.
Misc bots
/push or /updateBot with a key made for that bot. Misc bots of the type Key or Other are plain listings you fill in on the dashboard, they have nothing to push.Formats that are recognised
| Format | How it looks |
|---|---|
| Levelup card bot (catalog) | { selling: { tf2, tod, gem }, buying: { … }, sets: { appId: count } } or foil_sets |
| Gem bot (catalog) | { selling: { tf2, tod, … }, buying: { … }, gemStock }, values in gems |
| Crypto bot (catalog) | { selling: { tf2 }, buying: { tf2 }, cryptoAccepted: [...], stock: { tf2 } }, values in USD |
| Flat fields | { status, buy_sets_tf2, sell_gems_key_tf2, stock_gems, buy_crypto_tf2, … }, see the table under /bot/update.php |
| This site's own fields | tf2SetsPerKey, gemStock, tf2KeyToGems, buyPriceUsd, … as on the endpoints below |
| Short query names | tf2rate, tf2buyrate, gemrate, … (send them as JSON fields here) |
selling and buying alone fit all three kinds of bot, so for those the bot your key belongs to decides how the numbers are read. A body that clearly belongs to another kind of bot (for example sets per game sent with a gem bot's key) is refused with a 400 that says so, instead of being stored wrongly.
Only the levelup catalog is treated as a full snapshot: a currency it no longer lists is switched off on your listing. Every other format is a partial update, fields you leave out keep their last value and 0 or null clears one.
Address names
Bot software that posts to its own address name works too: https://steamtradeindex.com/api/v1/YOUR_API_KEY/update_gems, https://steamtradeindex.com/api/v1/YOUR_API_KEY/gems-update.php, https://steamtradeindex.com/api/v1/YOUR_API_KEY/update_script, https://steamtradeindex.com/api/v1/YOUR_API_KEY/pushCrypto, https://steamtradeindex.com/api/v1/YOUR_API_KEY/sync and similar. Upper or lower case, underscores, dashes and a .php ending are ignored. A name with gem or crypto in it is only a hint, used when the key and the data do not already say which bot this is. A name with inventory or sets plus a sets_data map goes to the inventory update. Names that do not look like an update (for example login) answer 404.
curl -X POST "https://steamtradeindex.com/api/v1/YOUR_API_KEY/push" -H "Content-Type: application/json" -d '{ "selling": { "tf2": 4000 }, "buying": { "tf2": 6000 }, "gemStock": 2550000 }'{ "success": true, "message": "Bot data updated.", "detected": { "category": "gem", "dialect": "catalog" } }detected tells you how the site read your data, which is the first thing to check when a listing looks wrong. Failures answer { "success": false, "message": "…" }, see Errors below.
/api/v1/bot/update.php
FREEOne address for card, gem and crypto bots. Short field names, key in a header.
The site looks at which bot the key belongs to and applies the fields that fit it. Updates are partial: only the fields you send change. A value of 0 or null clears a rate. The key can also be sent as X-API-Key or ?api_key=. With an account-wide key and several bots, add bot_id. This is the same logic as /push (key in the path), so the other formats listed there work here too.
Any bot
| Field | Meaning |
|---|---|
| status | "online", "busy", "offline" or "maintenance" |
Gem bot
| Field | Meaning |
|---|---|
| buy_gems_key_tf2 | Gems the bot pays you for one TF2 key |
| sell_gems_key_tf2 | Gems the bot charges for one TF2 key |
| buy_gems_key_tod / sell_gems_key_tod | The same for Tour of Duty tickets. Leave out if not traded |
| buy_gems_key_csgo / sell_gems_key_csgo, buy_gems_key_hydra / sell_gems_key_hydra | The same for CS2 and Hydra keys |
| stock_gems | Gems in stock. Leave it out and we read your Steam inventory |
Card or holiday bot
| Field | Meaning |
|---|---|
| buy_sets_tf2 / sell_sets_tf2 | Sets a trader gets for one TF2 key / sets the bot takes for a key it pays |
| buy_sets_tod / sell_sets_tod | The same for Tour of Duty tickets |
| buy_sets_csgo / sell_sets_csgo, buy_sets_hydra / sell_sets_hydra | The same for CS2 and Hydra keys |
| buy_gems_set / sell_gems_set | Gems a trader pays for one set / gems the bot pays for one set |
| stock_sets | Complete sets in stock. Leave it out and we read your Steam inventory |
| stock_badges | Optional: sets per game as { "730": 15, "570": 42 } |
| max_level | Optional: the highest level the bot can take a customer to |
Crypto bot
| Field | Meaning |
|---|---|
| buy_crypto_tf2 | USD the bot pays for one TF2 key |
| sell_crypto_tf2 | USD the bot charges for one TF2 key. Leave out if it does not sell |
| stock_keys_tf2 | TF2 keys in stock |
| other_rates.supported_crypto | Array of currency codes, for example ["BTC","ETH","USDT"] |
curl -X POST "https://steamtradeindex.com/api/v1/bot/update.php" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "status": "online", "buy_gems_key_tf2": 4000, "sell_gems_key_tf2": 6000, "stock_gems": 2550000 }'{ "success": true, "message": "Bot data updated.", "detected": { "category": "gem", "dialect": "flat" } }Failures answer { "success": false, "message": "…" } with a matching status: 401 for a wrong key, 400 for a value that is not valid or data that belongs to another kind of bot, 404 if the bot does not exist, 409 when the account has several bots and none was chosen. The other endpoints' own field names (for example tf2KeyToGems) are accepted here too.
/updateBot
FREEPush rates (and optionally stock) for a card or holiday bot.
Every field is optional: leave one out to keep it unchanged, send null (or 0, which the site treats as "no rate") to clear it. Send botStatus to say you are going offline or into maintenance right away. Unknown fields are ignored and listed back in an ignored array so typos are visible. Bot software that sends { selling, buying, sets } is accepted as is; because that body lists everything the bot currently trades, a currency it leaves out is switched off on your listing. Not sure which format your software uses? Use /push.
Fields
| Field | Meaning | levelup-bot .env |
|---|---|---|
| tf2SetsPerKey | Card sets a trader gets per TF2 key | RATES_TF2_SELL |
| csSetsPerKey | Card sets per CS2 key | RATES_CS2_SELL |
| hydraSetsPerKey | Card sets per Hydra key | RATES_HYDRA_SELL |
| todRate | Card sets per Tour of Duty ticket | RATES_TOD_SELL |
| tf2BuySetsPerKey | Card sets the bot takes from a trader per TF2 key it pays | RATES_TF2_BUY |
| csBuySetsPerKey, hydraBuySetsPerKey, todBuyRate | Same, for CS2 keys, Hydra keys, Tour of Duty tickets | RATES_CS2_BUY, RATES_HYDRA_BUY, RATES_TOD_BUY |
| gemBuyPrice | Gems a trader pays to buy one set | RATES_GEMS_SELL |
| gemSellPrice | Gems the bot pays for one set a trader sells | RATES_GEMS_BUY |
| maxSetsPerTrade | Largest trade the bot accepts, in sets | USER_PERMISSIONS_MAX_SETS_* |
| tf2BuyCommand, tf2SellCommand | Exact chat commands shown on your page (also for cs, hydra, gems) | - |
| botStatus | "online", "offline" or "maintenance" | - |
| setStock / stock | Complete sets in stock, see the stock note below | - |
curl -X POST "https://steamtradeindex.com/api/v1/YOUR_API_KEY/updateBot" \
-H "Content-Type: application/json" \
-d '{
"tf2SetsPerKey": 6,
"tf2BuySetsPerKey": 7,
"csSetsPerKey": null,
"gemBuyPrice": 900,
"gemSellPrice": 300,
"maxSetsPerTrade": 500,
"botStatus": "online"
}'{ "ok": true, "botId": "…", "stats": { "tf2SetsPerKey": 6, … }, "ignored": [] }Stock
setStock, stock or cardStock), we show your numbers straight away, and every few hours we check them against your public Steam inventory. Reporting the same or fewer sets than the inventory holds is fine (you may keep some in reserve). Reporting clearly more makes us show the inventory count and ignore your stock until a later check agrees. Keep your inventory public so your stock can be verified. Rates are never read from Steam, they only come from you.Stay visible
/updateinventory
FREEPush per-game complete-set counts (sets_data).
The compact stock format most bot software already builds: sets_data maps a Steam appId to the number of complete sets in stock. We add them up for your total, show the per-game breakdown on your page, and use it instead of our scan for 2 hours. Games at 0 are dropped; send { "sets_data": {} } when sold out. Accepts JSON, or form-data with the JSON in an inventory field.
curl -X POST "https://steamtradeindex.com/api/v1/YOUR_API_KEY/updateinventory" \
-H "Content-Type: application/json" \
-d '{ "sets_data": { "730": 15, "570": 42, "440": 0 } }'{ "ok": true, "setStock": 57, "gamesFound": 2 }/updateGemBot
FREEPush rates and stock for a gem bot.
| Field | Meaning |
|---|---|
| gemStock | Gems in stock. Leave it out and we read your gem balance from your Steam inventory. |
| tf2KeyToGems | Gems the bot pays you for one TF2 key (you sell it a key) |
| tf2KeySellGems | Gems the bot charges for one TF2 key (you buy a key from it) |
| todBuyGems / todSellGems | Gems the bot pays / charges per Tour of Duty ticket. Leave out if it doesn't trade tickets |
| csKeyToGems / hydraKeyToGems | Same as tf2KeyToGems, for CS2 and Hydra keys |
| botStatus | "online", "offline" or "maintenance" |
curl -X POST "https://steamtradeindex.com/api/v1/YOUR_API_KEY/updateGemBot" \
-H "Content-Type: application/json" \
-d '{ "gemStock": 50000, "tf2KeyToGems": 3400, "tf2KeySellGems": 3600, "botStatus": "online" }'Whole numbers only: a decimal rate is refused with a 422. Unknown fields are ignored and listed back in an ignored array; a request with no gem bot field at all is refused with a 400.
Already have a catalog script?
{ selling: { tf2, csgo, hydra, tod }, buying: { tf2, csgo, hydra, tod }, gemStock } as is: selling is the gems the bot pays per key or ticket it buys, buying is the gems it charges for one it sells. A value of 0 means not offered. Only the address and key change./updateCryptoBot
FREEPush rates and stock for a crypto bot.
| Field | Meaning |
|---|---|
| acceptedCurrencies | List of coins the bot accepts, e.g. ["BTC","ETH"] |
| itemStock | Items in stock |
| feePercent | Fee the bot charges, in percent |
| minTradeUsd | Smallest trade the bot accepts, in USD |
| buyPriceUsd | USD the bot pays for one TF2 key (customer sells to the bot) |
| sellPriceUsd | USD the bot charges for one TF2 key. Leave out, or send null, if the bot doesn't sell |
| networkFeeUsd | Flat network/payout fee in USD, if the bot charges one |
| keyCapacity | Most keys the bot will hold, shown next to its stock |
| botStatus | "online", "offline" or "maintenance" |
curl -X POST "https://steamtradeindex.com/api/v1/YOUR_API_KEY/updateCryptoBot" \
-H "Content-Type: application/json" \
-d '{ "acceptedCurrencies": ["BTC","ETH","USDT"], "itemStock": 1200, "buyPriceUsd": 1.57, "sellPriceUsd": 1.69, "networkFeeUsd": 0.5, "keyCapacity": 2000, "botStatus": "online" }'Unknown fields are ignored and listed back in an ignored array; a request with no crypto bot field at all is refused with a 400.
Already have a catalog script?
{ selling: { tf2 }, buying: { tf2 }, cryptoAccepted, stock: { tf2 } } as is. Prices are USD per key: selling is what the bot pays for a key it buys, buying what it charges for one it sells. Only TF2 keys are kept on this site./getBots
FREEPublic directory of bots with current stats.
List approved, publicly visible bots and their stats. Needs an API key (any key of yours works), and the request counts against that key's limit. Optional query category (card, gem, crypto, holiday, misc). Hidden bots and hidden owner names are excluded, same as on the site.
curl "https://steamtradeindex.com/api/v1/YOUR_API_KEY/getBots?category=card"{
"count": 42,
"bots": [
{
"id": "…", "slug": "example-bot", "name": "Example Bot", "category": "card",
"stats": { "tf2SetsPerKey": 6, "setStock": 1060, "gemBuyPrice": 900, … },
"dataStatus": "ok", "lastUpdatedAt": "2026-10-16T20:26:08.852Z"
}
]
}Rate limits
Limits are per API key, per minute.
| Endpoint | Limit |
|---|---|
| /push, /bot/update.php, /updateBot, /updateGemBot, /updateCryptoBot | 120 requests per minute |
| /updateinventory, /getBots | 60 requests per minute |
All of a key's requests share one counter, so polling /getBots with the key your bot pushes with uses up its pushes. Use a separate key for reading.
Value limits
A request body can be at most 256,000 characters (413 above that). A value outside these limits answers a 422 that names the field.
| Field | Limit |
|---|---|
| Stock fields (setStock, gemStock, itemStock, ...) | Whole numbers, 0 to 1,000,000,000 |
| Gem rates (gemBuyPrice, gemSellPrice, tf2KeyToGems, ...) | Whole numbers, 0 to 1,000,000,000 |
| Card set rates (tf2SetsPerKey, todRate, ...) | 0 to 1000, decimals allowed |
| maxSetsPerTrade | Whole number, 1 to 100,000 |
| Crypto prices (buyPriceUsd, sellPriceUsd, networkFeeUsd) | USD, 0 to 100,000, decimals allowed |
| feePercent / minTradeUsd / keyCapacity | 0 to 100 / 0 to 1,000,000 / whole number 0 to 1,000,000 |
| acceptedCurrencies | Up to 50 codes, each up to 20 characters |
| sets_data / sets | Keys are numeric Steam app ids, at most 500 games, each count a whole number 0 to 1,000,000 |
| Chat commands (tf2BuyCommand, ...) | Up to 60 characters |
Errors
/push and /bot/update.php answer { "success": false, "message": "…" } with the same status codes; the other endpoints add the error code shown below.
| Status | error | What to do |
|---|---|---|
| 401 | invalid_api_key | The key is wrong or was revoked. Copy it again from API keys. |
| 404 | not_found | No matching bot on this key's account (or the bot was deleted). |
| 409 | multiple_bots | Account-wide key with several bots: add botId, or use a key made for one bot. |
| 409 | wrong_category | This endpoint doesn't match the bot's category (card, gem, crypto). Use /push, or the key made for that bot. |
| 400 | wrong_category / invalid_payload | Returned instead of 409/422 by /push, /bot/update.php and the levelup catalog format on /updateBot, because that software only shows the message for 400. The message says what to fix. |
| 400 | no_recognised_fields | The request had none of the fields this kind of bot uses (or was empty), so nothing was stored. The message lists the accepted fields. |
| 413 | payload_too_large | The body is over 256,000 characters. |
| 422 | invalid_payload | A field has the wrong type. The message names it. |
| 429 | rate_limited | Too many requests. Slow down; limits are per key, per minute. |