Sends your bank, worn equipment and inventory to an HTTP endpoint you configure, so your own tools can answer questions like "do I have the materials for this?" without you checking manually.
No public API exposes item contents, so if you want your bank readable by something outside the game — a spreadsheet, a Discord bot, an assistant — the data has to come from the client.
This plugin will not be distributed through the RuneLite Plugin Hub. Their rejected features list disallows "plugins which expose player information over HTTP", which is precisely what this does, so the submission was declined. That is RuneLite's distribution policy rather than a Jagex rule, but it means the only way to run this is to load it yourself.
Two options, both of which need developer mode:
# Build and run a development client with the plugin loaded
./gradlew run
# Or sideload the jar into a client you launch yourself
./gradlew build
mkdir -p ~/.runelite/sideloaded-plugins
cp build/libs/*.jar ~/.runelite/sideloaded-plugins/
java -ea -jar RuneLite.jar --developer-modeDeveloper mode requires the --developer-mode flag, assertions (-ea), and a client not
started by an official launcher — any launcher sets runelite.launcher.version, which disables
developer mode and with it sideloading. RUNELITE_HOME=/path/containing/.runelite ./gradlew run
points the development client at an existing RuneLite directory so it keeps your installed plugins
and settings.
Nothing is sent until you configure an endpoint URL and a token. With both blank the plugin does nothing.
It POSTs JSON to the single URL you set, with your token in an X-Sync-Token header:
{
"username": "YourName",
"timestamp": "2026-07-27T14:32:05Z",
"containers": {
"bank": [ { "id": 1613, "name": "Red topaz", "quantity": 14 } ],
"inventory": [ { "id": 995, "name": "Coins", "quantity": 1842305 } ],
"equipment": [ { "id": 4151, "name": "Abyssal whip", "quantity": 1, "slot": "weapon" } ]
},
"inventory_slots_used": 24,
"inventory_slots_total": 28,
"pouch_contents": [ { "id": 563, "name": "Law rune", "quantity": 12 } ],
"quest_journal": {
"quest": "Dragon Slayer II",
"progress_var": 42,
"lines": [ "<str>Retrieve the pieces of the Dragonkin Cog.", "Speak to Fossegrimen." ]
},
"quests_in_progress": [ { "quest": "Dragon Slayer II", "progress_var": 42 } ]
}The container lists merge stacks by item id, so their length counts item types rather than
slots: four karambwans fill four inventory slots but arrive as one entry. inventory_slots_used
and inventory_slots_total are therefore counted on the raw slot array before merging, and the
total comes from the container itself rather than assuming 28.
pouch_contents is present only while a rune pouch is actually in the inventory. The inventory
reports a pouch as one opaque item, so its runes are read from the same varbits RuneLite's own
runepouch overlay uses (RUNE_POUCH_TYPE_1..6 / RUNE_POUCH_QUANTITY_1..6, resolved through enum
RUNEPOUCH_RUNE). The varbits keep their last values after a pouch is banked, so the field is
omitted rather than reporting runes you are not carrying.
quest_journal is sent only when you open a quest's page in your in-game quest journal, and only
if that quest is not yet finished — a completed quest's journal adds nothing, since completion
already comes from Quest.getState, and clicking through old quests happens often enough that
storing them would spend write budget for nothing. lines is the journal's raw text, markup and
all: a <str> tag marks a step as done, opening at the start of the line and never closed — the
game does not emit a matching </str>. progress_var is a per-quest varbit or varp value used
purely as a fingerprint — its meaning differs quest to quest and it is never decoded into a game
state itself, only compared against a later reading of the same var to tell whether the captured
text is still current. The field is omitted entirely, not sent as null, for the handful of
quests with no mapped var — Gson drops a null field rather than serialising it, and the receiving
schema treats it as optional accordingly. The table mapping
each quest to its var is ported from
Quest Helper (BSD 2-Clause, © Zoinkwiz), which
maintains the same mapping for its own step tracking.
quests_in_progress rides along on every push, not only journal opens, and is the plugin's full
current list of in-progress quests with their vars — it lets a receiver notice that a quest has
moved on since a journal was captured, even though the journal itself is not re-sent every time.
That is the whole network surface. It contacts no other host, and it reads no chat, no location, and no account credentials.
| Container | Trigger |
|---|---|
| Bank | bank contents change |
| Equipment | worn gear changes, and once on every login |
| Inventory | never on its own — included alongside a bank or equipment sync |
| Quest journal | you open a quest's page in the in-game quest journal |
The first three are container-change events; the quest journal is different — it fires on a widget opening, not on any container changing.
Equipment also syncs once per login because container events only fire on change: a player who never swaps gear would otherwise never report any, and the first snapshot would wait indefinitely on an unrelated event.
Inventory does not trigger uploads because it changes every few seconds while playing, which would mean constant traffic for no benefit. Including it with bank and equipment syncs is enough to answer "do I own this".
Uploads are debounced for 3 seconds, so opening the bank sends one request rather than one per tick.
- Placeholders are skipped. A bank placeholder is a reserved empty slot; reporting it would claim you own an item you have actually spent.
- Noted and un-noted stacks are merged. Item ids are canonicalised via
ItemManager, so 100 noted sharks and 5 loose ones report as 105 sharks under the wiki's name for the item. - Container contents are captured when the change event fires, not when the debounced upload runs — otherwise closing the bank within those 3 seconds drops it from the payload.
- Non-standard worlds are skipped: tournament, beta, deadman, PvP Arena, seasonal, fresh start, Last Man Standing and quest speedrunning. Items there are not your real account's.
- Failures are silent. Network errors are written to the RuneLite log and never interrupt gameplay.
| Setting | Meaning |
|---|---|
| Endpoint URL | Where to POST. Blank disables the plugin. |
| Sync token | Sent as X-Sync-Token. Blank disables the plugin. |
| Username override | Defaults to your logged-in display name. |
| Sync bank | Upload the bank when it changes. |
| Sync equipment | Upload worn gear when it changes. |
| Include inventory | Include the inventory with those uploads. |
| Sync quest journal | Send a quest's journal text when you open its page in the quest journal. Finished quests are skipped. |
Any HTTP server that accepts a JSON POST works. Your endpoint should check the X-Sync-Token
header against a value only you know, since that header is the only thing protecting it from
anyone who learns the URL.
A bank of ~670 distinct items serialises to roughly 40 KB, so a receiver that stores snapshots should compare a hash of the contents before rewriting anything — most syncs carry data identical to the previous one.
./gradlew build # jar in build/libs/
./gradlew test
./gradlew run # developer-mode client with the plugin loadedBSD 2-Clause. See LICENSE.