The service that carries Grand Exchange fills, player trades and loadout snapshots from the OSRS Toolkit RuneLite plugin to OSRS Toolkit. The web app and the desktop build both collect from here, over the same contract and with the same transport.
It is a queue and nothing more. Events live here only until the toolkit collects them, and current-state rows — your Grand Exchange slots, the offer box you have open — are overwritten in place rather than accumulating. Nothing is kept that has already been taken.
This repository is public on purpose. The RuneLite Plugin Hub accepts plugins that send data to a web service on the grounds that what is sent can be verified; the code that receives it is here so that verification does not have to stop at the plugin.
Per paired user, at most:
- Undelivered sync events, deleted the moment the toolkit confirms it has them, and expiring after 30 days regardless.
- The current eight Grand Exchange slots.
- The Grand Exchange offer box currently open, if any.
- The most recent bank/gear/skills snapshot, if that opt-in setting is enabled.
- A display name and a last-seen time, so the toolkit can show whether the plugin is connected.
There is no account, no password and no email. A pairing token is the only identifier, and it is stored as a SHA-256 digest — a copy of the database is not a set of working credentials.
python -m venv .venv && .venv/bin/pip install -e ".[dev]"
python -m sync_serverListens on 127.0.0.1:8000 by default and speaks plain HTTP. Put a tunnel or a reverse proxy in
front of it for anything reachable from outside the machine — it does not terminate TLS itself.
Issue yourself a pairing token:
curl -X POST http://127.0.0.1:8000/v1/pair -H 'content-type: application/json' -d '{}'All optional. Defaults suit one person running this for themselves.
| Variable | Default | |
|---|---|---|
SYNC_DATABASE |
sync.db |
Where SQLite lives |
SYNC_HOST / SYNC_PORT |
127.0.0.1 / 8000 |
Bind address |
SYNC_INVITE_CODE |
unset | Setting it closes pairing to people who know the code |
SYNC_RATE_LIMIT |
120 |
Requests per minute per token |
SYNC_EVENT_RETENTION_DAYS |
30 |
Uncollected events expire after this |
SYNC_TOKEN_RETENTION_DAYS |
180 |
A silent pairing and its data are removed after this |
SYNC_MAX_EVENTS |
20000 |
Queue cap per token; oldest dropped first |
SYNC_HOME_URL |
https://runescope.app |
Where a browser hitting / is sent; empty for a plain 404 |
SYNC_DOCS |
unset | Set it to serve the interactive docs at /docs |
Pairing is open by default because the plugin has to work for anyone who installs it from the
Plugin Hub. Set SYNC_INVITE_CODE while the service is only for you and people you know.
The endpoint contract is documented in the OSRS Toolkit repository at
docs/sync-api.md.
Only /v1/ answers. The interactive docs are off unless SYNC_DOCS is set — this service has
to be reachable from the open internet, since every installed plugin calls it from someone
else's machine, but its callers are the plugin and the web app, and neither reads a schema. A
browser landing on / is sent to the website instead.
Two decisions worth knowing before reading the code:
Events are stored and returned verbatim. Only the envelope is validated — an id to make retries idempotent, a timestamp to order by. A plugin that starts sending a new event type does not need this service updated to carry it.
Acking names ids rather than advancing a cursor. The toolkit deliberately leaves event types it does not recognise in the queue for a later build to import, and a cursor would sweep those away behind it.
.venv/bin/python -m pytest
.venv/bin/python -m ruff check . && .venv/bin/python -m ruff format --check .GNU Affero General Public License v3.0.
AGPL rather than GPL because this is software people reach over a network and never install. Under the GPL, running a modified copy as a service creates no obligation to share it; under the AGPL it does. Anyone offering this service to others has to offer them its source too — which is the same promise the Plugin Hub was given about what happens to the data.