steamtradeindex
Join our Discord for support, bot news and updates! Join Now
Developers

SteamTradeIndex API

Let your bot keep its own rates and stock up to date, or read the directory from your own tools. Updating your bot is free, you only need an API key.

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

  1. Open Dashboard → API keys, choose your bot under Key for and click Generate key. You can view and copy it there any time.
  2. Put the key into your bot software, or use the ready-made sender in Connect your own bot, so it sends your rates.
  3. Check your bot's page on the site, the rates and a fresh "updated" time should appear.
Try it with curl (card bot)
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?

The example above is for a card bot. A gem bot sends 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?

Levelup-style bot software that already reports its catalog to other directories sends its rates and stock as { 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.

  1. Create a key for your bot on API keys. A key made for one bot always updates that bot, no bot id needed.
  2. 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.
  3. 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.js
// 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
# 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

A good push answers { "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.

.env
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
// 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:

URL
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 botId to 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.
POST

/api/v1/{apiKey}/push

FREE

One 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

Foil and random card bots (listed under Misc) push exactly like a card bot: use /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

FormatHow 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 fieldstf2SetsPerKey, gemStock, tf2KeyToGems, buyPriceUsd, … as on the endpoints below
Short query namestf2rate, 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.

Request, any bot
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 }'
Response
{ "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.

POST

/api/v1/bot/update.php

FREE

One 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

FieldMeaning
status"online", "busy", "offline" or "maintenance"

Gem bot

FieldMeaning
buy_gems_key_tf2Gems the bot pays you for one TF2 key
sell_gems_key_tf2Gems the bot charges for one TF2 key
buy_gems_key_tod / sell_gems_key_todThe 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_hydraThe same for CS2 and Hydra keys
stock_gemsGems in stock. Leave it out and we read your Steam inventory

Card or holiday bot

FieldMeaning
buy_sets_tf2 / sell_sets_tf2Sets a trader gets for one TF2 key / sets the bot takes for a key it pays
buy_sets_tod / sell_sets_todThe same for Tour of Duty tickets
buy_sets_csgo / sell_sets_csgo, buy_sets_hydra / sell_sets_hydraThe same for CS2 and Hydra keys
buy_gems_set / sell_gems_setGems a trader pays for one set / gems the bot pays for one set
stock_setsComplete sets in stock. Leave it out and we read your Steam inventory
stock_badgesOptional: sets per game as { "730": 15, "570": 42 }
max_levelOptional: the highest level the bot can take a customer to

Crypto bot

FieldMeaning
buy_crypto_tf2USD the bot pays for one TF2 key
sell_crypto_tf2USD the bot charges for one TF2 key. Leave out if it does not sell
stock_keys_tf2TF2 keys in stock
other_rates.supported_cryptoArray of currency codes, for example ["BTC","ETH","USDT"]
Request, gem bot
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 }'
Response
{ "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.

POST

/updateBot

FREE

Push 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

FieldMeaninglevelup-bot .env
tf2SetsPerKeyCard sets a trader gets per TF2 keyRATES_TF2_SELL
csSetsPerKeyCard sets per CS2 keyRATES_CS2_SELL
hydraSetsPerKeyCard sets per Hydra keyRATES_HYDRA_SELL
todRateCard sets per Tour of Duty ticketRATES_TOD_SELL
tf2BuySetsPerKeyCard sets the bot takes from a trader per TF2 key it paysRATES_TF2_BUY
csBuySetsPerKey, hydraBuySetsPerKey, todBuyRateSame, for CS2 keys, Hydra keys, Tour of Duty ticketsRATES_CS2_BUY, RATES_HYDRA_BUY, RATES_TOD_BUY
gemBuyPriceGems a trader pays to buy one setRATES_GEMS_SELL
gemSellPriceGems the bot pays for one set a trader sellsRATES_GEMS_BUY
maxSetsPerTradeLargest trade the bot accepts, in setsUSER_PERMISSIONS_MAX_SETS_*
tf2BuyCommand, tf2SellCommandExact chat commands shown on your page (also for cs, hydra, gems)-
botStatus"online", "offline" or "maintenance"-
setStock / stockComplete sets in stock, see the stock note below-
Request
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"
  }'
Response
{ "ok": true, "botId": "…", "stats": { "tf2SetsPerKey": 6, … }, "ignored": [] }

Stock

If you don't send stock, we read your bot's public Steam inventory for it automatically. If you do send 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

Push whenever something changes, plus a regular heartbeat (every 25 to 55 minutes is ideal, with a random gap). Between heartbeats, push only when something changed. A bot that has not been heard from for 2 hours is hidden from the directory until its next push.
POST

/updateinventory

FREE

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

Request
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 } }'
Response
{ "ok": true, "setStock": 57, "gamesFound": 2 }
POST

/updateGemBot

FREE

Push rates and stock for a gem bot.

FieldMeaning
gemStockGems in stock. Leave it out and we read your gem balance from your Steam inventory.
tf2KeyToGemsGems the bot pays you for one TF2 key (you sell it a key)
tf2KeySellGemsGems the bot charges for one TF2 key (you buy a key from it)
todBuyGems / todSellGemsGems the bot pays / charges per Tour of Duty ticket. Leave out if it doesn't trade tickets
csKeyToGems / hydraKeyToGemsSame as tf2KeyToGems, for CS2 and Hydra keys
botStatus"online", "offline" or "maintenance"
Request
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?

This endpoint also accepts { 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.
POST

/updateCryptoBot

FREE

Push rates and stock for a crypto bot.

FieldMeaning
acceptedCurrenciesList of coins the bot accepts, e.g. ["BTC","ETH"]
itemStockItems in stock
feePercentFee the bot charges, in percent
minTradeUsdSmallest trade the bot accepts, in USD
buyPriceUsdUSD the bot pays for one TF2 key (customer sells to the bot)
sellPriceUsdUSD the bot charges for one TF2 key. Leave out, or send null, if the bot doesn't sell
networkFeeUsdFlat network/payout fee in USD, if the bot charges one
keyCapacityMost keys the bot will hold, shown next to its stock
botStatus"online", "offline" or "maintenance"
Request
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?

This endpoint also accepts { 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.
GET

/getBots

FREE

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

Request
curl "https://steamtradeindex.com/api/v1/YOUR_API_KEY/getBots?category=card"
Response
{
  "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.

EndpointLimit
/push, /bot/update.php, /updateBot, /updateGemBot, /updateCryptoBot120 requests per minute
/updateinventory, /getBots60 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.

FieldLimit
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
maxSetsPerTradeWhole number, 1 to 100,000
Crypto prices (buyPriceUsd, sellPriceUsd, networkFeeUsd)USD, 0 to 100,000, decimals allowed
feePercent / minTradeUsd / keyCapacity0 to 100 / 0 to 1,000,000 / whole number 0 to 1,000,000
acceptedCurrenciesUp to 50 codes, each up to 20 characters
sets_data / setsKeys 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.

StatuserrorWhat to do
401invalid_api_keyThe key is wrong or was revoked. Copy it again from API keys.
404not_foundNo matching bot on this key's account (or the bot was deleted).
409multiple_botsAccount-wide key with several bots: add botId, or use a key made for one bot.
409wrong_categoryThis endpoint doesn't match the bot's category (card, gem, crypto). Use /push, or the key made for that bot.
400wrong_category / invalid_payloadReturned 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.
400no_recognised_fieldsThe request had none of the fields this kind of bot uses (or was empty), so nothing was stored. The message lists the accepted fields.
413payload_too_largeThe body is over 256,000 characters.
422invalid_payloadA field has the wrong type. The message names it.
429rate_limitedToo many requests. Slow down; limits are per key, per minute.