{
  "openapi": "3.1.0",
  "info": {
    "title": "SteamTradeX Listing API",
    "version": "1.0.0",
    "summary": "A listed bot reports its own rates and stock to its listing. Free.",
    "description": "Free for every bot listed on SteamTradeX. One key per listing, issued in Listed Bots, Edit Bot. Keys start stx_.\n\nAt most one update every 3 seconds per bot. A bot that has not reported for 8 hours moves to the Missing Data table.\n\nThis is the free Listing API. It does not read inventories: that is the paid Inventory API (keys start inv_), see https://steamtradex.com/docs/inventory-api.",
    "contact": {
      "name": "SteamTradeX",
      "url": "https://steamtradex.com/docs/listing-api"
    }
  },
  "externalDocs": {
    "description": "Full reference",
    "url": "https://steamtradex.com/docs/listing-api"
  },
  "servers": [
    {
      "url": "https://steamtradex.com"
    }
  ],
  "tags": [
    {
      "name": "Listing",
      "description": "Keep your bot's listing current."
    },
    {
      "name": "Card sets",
      "description": "How many cards make a set, per game."
    }
  ],
  "paths": {
    "/api/v1/bots/update": {
      "post": {
        "tags": [
          "Listing"
        ],
        "operationId": "updateListing",
        "summary": "Report your bot's rates and stock",
        "description": "Updates the one live listing the key belongs to. Advertising a higher rate than the rate checker measured clears the listing's Rate checked mark until it is checked again.",
        "security": [
          {
            "bearer": []
          },
          {}
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "anyOf": [
                  {
                    "type": "object",
                    "description": "Send only what you have. Fields you leave out keep their last value. At least one field other than games_in_stock.",
                    "properties": {
                      "sets_in_stock": {
                        "oneOf": [
                          {
                            "type": "integer"
                          },
                          {
                            "type": "string",
                            "pattern": "^\\d{1,12}$"
                          }
                        ],
                        "description": "Total card sets the bot holds. (0 to 100,000,000)"
                      },
                      "games_in_stock": {
                        "oneOf": [
                          {
                            "type": "integer"
                          },
                          {
                            "type": "string",
                            "pattern": "^\\d{1,12}$"
                          }
                        ],
                        "description": "How many games those sets are from. Not enough on its own: send it with something else. (0 to 50,000)"
                      },
                      "max_level": {
                        "oneOf": [
                          {
                            "type": "integer"
                          },
                          {
                            "type": "string",
                            "pattern": "^\\d{1,12}$"
                          }
                        ],
                        "description": "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. (0 to 6,000)"
                      },
                      "buy_rate": {
                        "oneOf": [
                          {
                            "type": "number"
                          },
                          {
                            "type": "string",
                            "pattern": "^\\d{1,9}(\\.\\d{1,6})?$"
                          }
                        ],
                        "description": "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. (1 to 500)"
                      },
                      "sell_rate": {
                        "oneOf": [
                          {
                            "type": "number"
                          },
                          {
                            "type": "string",
                            "pattern": "^\\d{1,9}(\\.\\d{1,6})?$"
                          }
                        ],
                        "description": "Sets a customer gives per TF2 key when selling to the bot. (1 to 500)"
                      },
                      "buy_gems_per_set": {
                        "oneOf": [
                          {
                            "type": "integer"
                          },
                          {
                            "type": "string",
                            "pattern": "^\\d{1,12}$"
                          }
                        ],
                        "description": "Whole gems a customer pays for one set when buying with gems. The larger of the two gem numbers. (10 to 100,000)"
                      },
                      "sell_gems_per_set": {
                        "oneOf": [
                          {
                            "type": "integer"
                          },
                          {
                            "type": "string",
                            "pattern": "^\\d{1,12}$"
                          }
                        ],
                        "description": "Whole gems a customer is paid for one set when selling to the bot. (10 to 100,000)"
                      },
                      "bot_id": {
                        "type": "string",
                        "pattern": "^7656119\\d{10}$",
                        "description": "Optional. Your bot's SteamID64. When sent, it must be the bot this key belongs to, or the update is refused with 403."
                      }
                    }
                  },
                  {
                    "type": "object",
                    "description": "Other field names we accept, for scripts written for another bot list. Mixed with ours, our name wins when both are sent.",
                    "properties": {
                      "secret": {
                        "type": "string",
                        "description": "Your Listing API key, in the body instead of the Authorization header. Only read when there is no Bearer header."
                      },
                      "bot_id": {
                        "type": "string",
                        "pattern": "^7656119\\d{10}$",
                        "description": "Optional. Your bot's SteamID64. When sent, it must be the bot this key belongs to, or the update is refused with 403."
                      },
                      "stockData": {
                        "type": "object",
                        "additionalProperties": {
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 10000000
                        },
                        "description": "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": {
                        "oneOf": [
                          {
                            "type": "number"
                          },
                          {
                            "type": "string",
                            "pattern": "^\\d{1,9}(\\.\\d{1,6})?$"
                          }
                        ],
                        "description": "The same as 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": {
                        "oneOf": [
                          {
                            "type": "number"
                          },
                          {
                            "type": "string",
                            "pattern": "^\\d{1,9}(\\.\\d{1,6})?$"
                          }
                        ],
                        "description": "The same as sell_rate. Sets a customer gives per TF2 key when selling to the bot."
                      },
                      "gems_sell_rate": {
                        "oneOf": [
                          {
                            "type": "integer"
                          },
                          {
                            "type": "string",
                            "pattern": "^\\d{1,12}$"
                          }
                        ],
                        "description": "The same as 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": {
                        "oneOf": [
                          {
                            "type": "integer"
                          },
                          {
                            "type": "string",
                            "pattern": "^\\d{1,12}$"
                          }
                        ],
                        "description": "The same as sell_gems_per_set. Whole gems a customer is paid for one set when selling to the bot."
                      }
                    }
                  }
                ]
              },
              "examples": {
                "ours": {
                  "summary": "Our field names",
                  "value": {
                    "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": {
                  "summary": "Other field names, key in the body",
                  "value": {
                    "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
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "updated_at"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "updated_at": "2026-10-07T12:00:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "Content-Type is not application/json, or the body is not a JSON object. The body is over 2 MB. bot_id is not 17 digits starting 7656119. stockData is not an object. More than 50,000 games in stockData. A key in stockData is not digits. A value in stockData is not a whole number from 0 to 10,000,000. The games add up to too many sets. sets_in_stock is out of range or not a whole number. games_in_stock is out of range or not a whole number. max_level is out of range or not a whole number. buy_rate (or tf2_buy_rate) is out of range. sell_rate (or tf2_sell_rate) is out of range. buy_gems_per_set (or gems_sell_rate) is out of range or not whole. sell_gems_per_set (or gems_buy_rate) is out of range or not whole. None of the fields was sent (games_in_stock alone does not count). A number got past the checks above but is still too big to store.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Refusal"
                },
                "examples": {
                  "e1": {
                    "summary": "send a JSON body",
                    "value": {
                      "ok": false,
                      "error": "send a JSON body"
                    }
                  },
                  "e2": {
                    "summary": "the body is too large: 2 MB at most",
                    "value": {
                      "ok": false,
                      "error": "the body is too large: 2 MB at most"
                    }
                  },
                  "e3": {
                    "summary": "bot_id must be a SteamID64",
                    "value": {
                      "ok": false,
                      "error": "bot_id must be a SteamID64"
                    }
                  },
                  "e4": {
                    "summary": "stockData must be an object of appid to sets",
                    "value": {
                      "ok": false,
                      "error": "stockData must be an object of appid to sets"
                    }
                  },
                  "e5": {
                    "summary": "stockData is too large",
                    "value": {
                      "ok": false,
                      "error": "stockData is too large"
                    }
                  },
                  "e6": {
                    "summary": "stockData keys must be app ids",
                    "value": {
                      "ok": false,
                      "error": "stockData keys must be app ids"
                    }
                  },
                  "e7": {
                    "summary": "stockData values must be whole numbers",
                    "value": {
                      "ok": false,
                      "error": "stockData values must be whole numbers"
                    }
                  },
                  "e8": {
                    "summary": "stockData adds up to more than 100,000,000 sets",
                    "value": {
                      "ok": false,
                      "error": "stockData adds up to more than 100,000,000 sets"
                    }
                  },
                  "e9": {
                    "summary": "sets_in_stock must be a whole number, 100,000,000 at most",
                    "value": {
                      "ok": false,
                      "error": "sets_in_stock must be a whole number, 100,000,000 at most"
                    }
                  },
                  "e10": {
                    "summary": "games_in_stock must be a whole number, 50000 at most",
                    "value": {
                      "ok": false,
                      "error": "games_in_stock must be a whole number, 50000 at most"
                    }
                  },
                  "e11": {
                    "summary": "max_level must be a whole number, 6000 at most",
                    "value": {
                      "ok": false,
                      "error": "max_level must be a whole number, 6000 at most"
                    }
                  },
                  "e12": {
                    "summary": "buy_rate must be sets per key, from 1 to 500",
                    "value": {
                      "ok": false,
                      "error": "buy_rate must be sets per key, from 1 to 500"
                    }
                  },
                  "e13": {
                    "summary": "sell_rate must be sets per key, from 1 to 500",
                    "value": {
                      "ok": false,
                      "error": "sell_rate must be sets per key, from 1 to 500"
                    }
                  },
                  "e14": {
                    "summary": "buy_gems_per_set must be whole gems per set, from 10 to 100000",
                    "value": {
                      "ok": false,
                      "error": "buy_gems_per_set must be whole gems per set, from 10 to 100000"
                    }
                  },
                  "e15": {
                    "summary": "sell_gems_per_set must be whole gems per set, from 10 to 100000",
                    "value": {
                      "ok": false,
                      "error": "sell_gems_per_set must be whole gems per set, from 10 to 100000"
                    }
                  },
                  "e16": {
                    "summary": "nothing to update: send stock or rates",
                    "value": {
                      "ok": false,
                      "error": "nothing to update: send stock or rates"
                    }
                  },
                  "e17": {
                    "summary": "a number in the report is out of range",
                    "value": {
                      "ok": false,
                      "error": "a number in the report is out of range"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, a wrong key, or the bot is not live on the list. All three get the same answer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Refusal"
                },
                "examples": {
                  "e1": {
                    "summary": "missing or bad key",
                    "value": {
                      "ok": false,
                      "error": "missing or bad key"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "bot_id is not the bot this key was issued for.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Refusal"
                },
                "examples": {
                  "e1": {
                    "summary": "this key belongs to a different bot",
                    "value": {
                      "ok": false,
                      "error": "this key belongs to a different bot"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Less than 3 seconds since this bot's last saved update.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Refusal"
                },
                "examples": {
                  "e1": {
                    "summary": "too soon: send at most one update every 3 seconds",
                    "value": {
                      "ok": false,
                      "error": "too soon: send at most one update every 3 seconds"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Our database had trouble. Nothing was saved.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Refusal"
                },
                "examples": {
                  "e1": {
                    "summary": "we could not save that just now: try again in a minute",
                    "value": {
                      "ok": false,
                      "error": "we could not save that just now: try again in a minute"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/card-sets": {
      "get": {
        "tags": [
          "Card sets"
        ],
        "operationId": "getCardSets",
        "summary": "Cards per set, per game",
        "description": "Public. A game missing from the answer is not known yet: ask again later. With your Listing API key as a Bearer header, games we do not know yet are queued to be looked up (up to 1,200 new games a key an hour).",
        "security": [
          {},
          {
            "bearer": []
          }
        ],
        "parameters": [
          {
            "name": "appids",
            "in": "query",
            "required": false,
            "description": "Comma separated app ids, at most 500. Leave it out for every game known so far.",
            "schema": {
              "type": "string",
              "pattern": "^\\d{1,10}(,\\d{1,10})*$"
            },
            "example": "440,570,730"
          }
        ],
        "responses": {
          "200": {
            "description": "App id to cards in one set. Cached for a day when complete, five minutes when some games are still unknown.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "integer"
                  }
                },
                "example": {
                  "440": 9,
                  "570": 8,
                  "730": 5
                }
              }
            }
          },
          "400": {
            "description": "appids is not a list of app ids, or too many.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "error": "appids must be a comma separated list of Steam app ids"
                }
              }
            }
          },
          "401": {
            "description": "A key was sent and it is wrong, or its bot is not live.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "error": "missing or bad key"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearer": {
        "type": "http",
        "scheme": "bearer",
        "description": "Your Listing API key (stx_...) as Authorization: Bearer stx_... On the update endpoint you may instead send it as the body's secret field."
      }
    },
    "schemas": {
      "Refusal": {
        "type": "object",
        "required": [
          "ok",
          "error"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": false
          },
          "error": {
            "type": "string"
          }
        }
      }
    }
  }
}