Skip to content
everloot
Get the addon

Market prices in a script

Read Forever market values from JavaScript or Python.

Prerequisites#

  • Node 18 or newer, for the built-in fetch, or Python 3 with only its standard library. Nothing to install in either case.
  • No account and no key. The endpoint is GET /api/market/prices.

Steps#

  1. Know the two answers

    Without a realm, the endpoint lists the realms that have public prices. With one, it returns the items with the most units listed on that realm.

    GET /api/market/prices
    {
      "ok": true,
      "dataAsOf": "2026-10-20T06:20:28.878Z",
      "realms": [],
      "attribution": { "licence": "CC BY-SA 4.0", "credit": "Data from Everloot (https://everloot.app), CC BY-SA 4.0.", "…": "…" }
    }

    Items are lists, not objects. fields gives the order, so read it rather than counting positions. Prices are copper per unit: 10,000 copper is 1 gold, 100 copper is 1 silver.

  2. Save the script

    Save one of these as prices.mjs or prices.py. It lists the realms when you give it none, and prints up to ten items when you give it a realm. It handles an empty list, and stops with the wait time on a 429.

    prices
    // Forever market values for one realm. Run: node prices.mjs "Realm name"
    const die = (message) => {
      console.error(message);
      process.exit(1);
    };
    const BASE = "https://everloot.app";
    const realm = process.argv[2];
    
    async function get(path) {
      const res = await fetch(`${BASE}${path}`);
      const body = await res.json();
      if (res.status === 429) die(`Rate limited. Try again in ${res.headers.get("retry-after")} seconds.`);
      if (!res.ok) die(`${res.status}: ${body.error ?? "request failed"}`);
      return body;
    }
    
    if (!realm) {
      // Without a realm the endpoint lists the realms that have public prices.
      const { realms } = await get("/api/market/prices");
      if (realms.length === 0) {
        console.log("No realm has public prices yet. A day is public once three players have scanned the realm.");
      } else {
        console.log("Realms with public prices:");
        for (const name of realms) console.log(`  ${name}`);
        console.log('Run again with a realm, for example: node prices.mjs "<realm>"');
      }
      process.exit(0);
    }
    
    const data = await get(`/api/market/prices?realm=${encodeURIComponent(realm)}`);
    if (data.items.length === 0) {
      console.log(`No public prices for ${realm} yet.`);
      process.exit(0);
    }
    
    // Each item is a tuple in the order given by data.fields.
    const rows = data.items.map((tuple) => Object.fromEntries(data.fields.map((name, i) => [name, tuple[i]])));
    const gold = (copper) => `${Math.floor(copper / 10000)}g ${Math.floor((copper % 10000) / 100)}s ${copper % 100}c`;
    for (const row of rows.slice(0, 10)) {
      console.log(`item ${row.itemId}: market ${gold(row.market)}, lowest ${gold(row.min)}, ${row.quantity} listed (${row.day})`);
    }
    console.log(data.attribution.credit);
  3. Run it

    Run it with no realm first.

    Run
    node prices.mjs

    Today both print this, which is what an empty answer looks like:

    Output
    No realm has public prices yet. A day is public once three players have scanned the realm.
  4. Ask for a realm

    Once a realm has public prices, the first run lists it, and you pass its name as the argument. Placeholder values:

    First run, then a realm
    Realms with public prices:
      <realm>
    Run again with a realm, for example: node prices.mjs "<realm>"

    A realm with no public prices, or one that does not exist, gives an empty items list, and the script says so.

Check your work#

  • With no realm, the script prints either the empty-answer line or a list of realm names. A stack trace means the request failed; read its first line.
  • With a realm, every line has an item id and three numbers. The copper amounts are whole numbers; if you see decimals, you are not reading this endpoint.
  • The last line is the credit line. Show it wherever you publish these values.
  • This endpoint allows 200 requests an hour for each IP address. Over that, the answer is a 429 with a Retry-After header in seconds, and the script prints it: Rate limited. Try again in 3132 seconds.

An empty list means no public values yet. It does not mean the price is zero.

Next steps#