Skip to content

Addon authors

xLN edited this page Oct 3, 2026 · 5 revisions

This page is for people writing their own addons. If you just play, you can skip it.

Goblinomics has a public API, so your addon can read wealth, bookings, crafting profit and farm sessions, react to events, or plug in its own price source. There's exactly one global, Goblinomics, and the API hangs off it:

local API = Goblinomics and Goblinomics.API and Goblinomics.API.v1

On clients older than 12.1 Goblinomics switches itself off and the global doesn't exist, so always check for nil.

The promise

Everything on this page is API.v1, and v1 stays compatible for the whole 1.x line. Names, arguments and payload fields won't be removed or change meaning. New functions and new payload fields can appear. If I ever need to break something, it goes into a v2 next to v1. Anything not documented here (the addons' private namespaces, SavedVariables layout) is internal and can change in any release. Please don't read GoblinomicsLedgerDB directly; I will rename things in there.

Add ## OptionalDeps: Goblinomics to your TOC so Goblinomics loads before you.

Events

API.On(event, handler, owner)   -- handler(event, payload)
API.Off(event, owner)

owner is required, a string like your addon's name. There's one handler per owner and event, so a second On with the same owner replaces the first. Treat payloads as read-only; they're shared with every other listener.

Event Payload From
MONEY_DELTA { scope = "player" | "warband", amount, total, time, context, restricted } Core
ITEMS_DELTA { changes, time, context, restricted } Core
LOOT_RECEIVED { itemKey, link, quantity, source, time, context, restricted } Core
CONTEXT_CHANGED { key, active } Core
RESTRICTED_ENTER / RESTRICTED_LEAVE { kind, time, ... } Core
PRICES_CHANGED {}, debounced Core
MODULE_STATE_CHANGED { id, enabled } Core
NETWORTH_UPDATED the wealth result: wealth, gold, auctions, items, speculative, chars, warband, remote Vault
VAULT_SPECULATIVE_UPDATED, VAULT_GOALS Vault
LEDGER_TRANSACTION a booking (see below), plus merged or imported Ledger
LEDGER_TRANSACTION_ITEM a booking that got its item attached later Ledger
LEDGER_CHANGED {}, debounced Ledger
LEDGER_AH_EVENT { kind, itemKey, quantity, amount, net, unitPrice } Ledger
GATHERER_SESSION { action, session } Gatherer
GATHERER_FARMS Gatherer
WORKSHOP_CRAFT, WORKSHOP_MATCH, WORKSHOP_ORDER Workshop
IMPORT_DONE { id, source, time, added, events, duplicates } Import

Amounts are always copper. LEDGER_AH_EVENT.kind is one of post, sale, purchase, expired, cancelled; for sales net is what actually reached your pocket.

Core events are not sent during boss encounters, Mythic+ and rated PvP. What happened in there is replayed afterwards, with restricted set.

A booking has time, char ("Name-Realm"), category (AH, Vendor, Repair, Quest, Loot, Crafting, Mail, Other, Transfer), sub, amount (signed), and optionally itemKey, quantity, tag, note.

Reading data

Module APIs only exist while that module is loaded, so check before calling.

API Functions
API.Vault Current(), History(), Snapshot(label), SetRemote(snap), RemoveRemote(id), Remotes(), ItemCount(itemKey)
API.Ledger Query(filter), Aggregates(fromKey), Import(batch, dryRun), RemoveImport(source), AuctionStats(itemKey, days)
API.Workshop Breakdown(filter), Daily(days, filter)
API.Gatherer Summaries(from)

Call these with a colon: API.Ledger:Query({ from = t, category = "AH" }). The same goes for API.Price and API.Value. API.Ledger:Query takes from (epoch seconds), char, category, tag and search, and returns bookings newest first. API.Vault:History() is keyed by "YYYY-MM-DD" with wealth, speculative, gold, chars and warband per day. API.Vault:ItemCount(itemKey) is how many of an item you own across all characters, bank and warband bank, bound copies left out. API.Ledger:AuctionStats(itemKey, days) sums up your own auctions of an item (posted, sold, expired, cancelled, revenue, depositLost, saleRate, averagePrice, lastSale), or returns nil if there are none; leave out days for all time.

A few helpers are useful on their own. API.KnownCharacters() lists every character of the account Goblinomics has seen. API.Money.Format(copper) formats copper the way Goblinomics does. API.ItemKey.FromLink(link) turns an item link into the key Goblinomics uses everywhere. Keys follow TSM's item string convention (i:2589, bonus IDs sorted after that, p: for caged pets), so you can pass them straight to TSM_API. API.Price:Get(itemKey, role) returns a price, API.Price:HasRole(role) tells you whether any source provides a role at all (only TSM has saleRate), API.Price:Query(itemKey, name) asks a source for a named price such as TSM's DBRecent (nil if no source knows the name), and API.Value:Evaluate(itemKey, { bound = false, quantity = 1 }) returns the valuation the Vault uses (unit, total, tier, rule).

A small example

-- MyAddon.toc: ## OptionalDeps: Goblinomics
local API = Goblinomics and Goblinomics.API and Goblinomics.API.v1
if not API then return end

local OWNER = "MyAddon"

API.On("NETWORTH_UPDATED", function(_, result)
    print("Wealth: " .. API.Money.Format(result.wealth)
        .. " (speculative: " .. API.Money.Format(result.speculative) .. ")")
end, OWNER)

API.On("LEDGER_AH_EVENT", function(_, e)
    if e.kind == "sale" then
        print(("Sold %d x %s, net %s"):format(e.quantity or 1, e.itemKey or "?", API.Money.Format(e.net or 0)))
    end
end, OWNER)

-- /myah: auction house income of the last 7 days
SLASH_MYADDON_AH1 = "/myah"
SlashCmdList.MYADDON_AH = function()
    if not API.Ledger then
        print("The Goblinomics Ledger is switched off.")
        return
    end
    local income = 0
    for _, b in ipairs(API.Ledger:Query({ from = time() - 7 * 86400, category = "AH" })) do
        if b.amount > 0 then income = income + b.amount end
    end
    print("AH income, 7 days: " .. API.Money.Format(income))
end

Adding a price source

Goblinomics asks price sources per role: market, destroy, saleRate (a fraction, 0.05 means 5 %) and vendor. For each role it walks through the sources and takes the first positive value. For market and destroy the source the player picked as preferred (TSM or Auctionator) comes first, then everything else by priority, lower first. TSM is 10, Auctionator 20, the built-in vendor source 100.

local API = Goblinomics and Goblinomics.API and Goblinomics.API.v1
if not API then return end

API.Price:RegisterSource({
    id = "myprices",
    name = "My Prices",
    priority = 50,
    roles = { market = true },
    Get = function(self, itemKey, role)
        local itemID = API.ItemKey.ToItemID(itemKey)
        return itemID and MyPriceTable[itemID] or nil   -- copper, or nil
    end,
    Status = function(self)                             -- optional
        return { available = MyPriceTable ~= nil, note = nil }
    end,
})

Get is called as source:Get(itemKey, role). Return copper, or nil. If you're waiting for item data, return nil, "pending" and Goblinomics will say so instead of treating the item as priceless. Results are cached, so when your data changes, call API.Price:Invalidate() (or API.Price:Invalidate(itemKey) for one item). That also triggers PRICES_CHANGED, and the Vault recalculates. Errors inside Get are caught and reported, but please don't rely on that.

The source shows up in Settings > Prices under "Price sources", with the note from Status if you return one.

Licence

Goblinomics is under the Mozilla Public License 2.0. Your own addon that talks to Goblinomics through this API is your code, in your files, and you can release it under whatever licence you want. The MPL only applies to the files of Goblinomics itself: if you ship a modified copy of one of them, that file stays MPL and its source has to be available. Please don't call your addon "Goblinomics" though, something like "Goblinomics: Mount Tracker" or "MyAddon for Goblinomics" is fine.

Questions

If something here is unclear or you need a hook that doesn't exist, open an issue at https://github.com/xLN1995/goblinomics/issues. I'd rather add a proper API function than have addons poking into private tables.

Clone this wiki locally