HTTP API

Everything BridgeServer knows is available over plain HTTP on HttpPort (default 55981). Responses are JSON, requests are GET only, and there is no write path.

Endpoints#

EndpointReturns
/v1/healthStatus, version, connected HTTP clients, events stored
/v1/streamLive event feed (Server-Sent Events)
/v1/feed/historyRecent events from the history store
/v1/statusAccounts, characters, guilds and the live online gauge
/v1/events/scheduleCurrent event timetable with countdowns
/v1/rankings/{kind}resets · grandresets · killers · level · guilds · online
/v1/character/{name}Public character profile
/v1/guild/{name}Guild profile with members
/v1/castle-siegeCurrent castle owner and siege state

Any other path returns 404. A ranking or profile request returns 503 when the game database is not configured or has not finished its first refresh.

Event Shape#

Every event is one flat JSON object:

{
  "t": 1754049312,
  "type": 23,
  "kind": "raredrop",
  "actor": { "name": "Nyx", "level": 400, "class": 16, "map": 30, "x": 128, "y": 128 },
  "item": 13,
  "itemName": "Dragon Soul",
  "itemLevel": 13,
  "exc": 1,
  "anc": 0,
  "map": 30, "x": 128, "y": 128
}
FieldAlways presentMeaning
tyesUnix timestamp
typeyesNumeric event type, used by the type= history filter
kindyesReadable event kind - switch on this
actormost kindsWho caused it: name, level, class, map, x, y
targetpair eventsThe other character, same shape as actor

Remaining fields depend on the kind. Consumers should switch on kind and ignore everything they do not recognise, so new event kinds never break an existing integration.

Event Kinds#

kindtypeFires when
login1A character comes online
logout2A character goes offline
chat3Public chat (channel: 0), when chat export is on
post3A global /post message (channel: 1)
stats4Population snapshot, every 30 seconds
schedule5Event timetable snapshot, every 60 seconds
pk10A player kills another player
bosskill11A configured boss class dies
mobkill12Any monster dies, when monster-kill export is on
gameevent13Blood Castle, Devil Square, Chaos Castle, an invasion or Castle Siege starts or ends
eventwin14An event is won
trade20A trade completes
pshopsell21A personal shop sale completes
chaosmix22A Chaos Machine attempt resolves
raredrop23An excellent, ancient or socketed item drops on the ground
sellpost24An item is listed through the sell-post channel
levelup30A character gains a level
reset grandreset masterreset31A character resets

stats and schedule are snapshots: they are streamed live and served from their own endpoints, but never written to history or relayed to Discord.

Live Feed#

/v1/stream is a standard Server-Sent Events endpoint. Each event arrives as one data: frame containing the JSON object.

const feed = new EventSource("http://your-host:55981/v1/stream");

feed.onmessage = (msg) => {
  const e = JSON.parse(msg.data);

  if (e.kind === "raredrop") {
    announce(`${e.actor.name} found ${e.itemName} +${e.itemLevel}`);
  }
};

Browsers reconnect automatically when the connection drops. Events that happened while you were disconnected are not replayed - use /v1/feed/history to fill a gap.

There is a fixed cap on simultaneous stream subscribers, and a slow subscriber is dropped rather than allowed to back everything up. Point one server-side consumer at the stream and fan out from there instead of opening a stream per website visitor.

History#

# last 100 events of any kind
curl "http://your-host:55981/v1/feed/history?limit=100"

# boss kills since a timestamp
curl "http://your-host:55981/v1/feed/history?type=11&since=1754049312"
ParameterDefaultEffect
typeallNumeric event type from the table above
limit50Maximum events returned. Values above 500 are capped at 500
since0Only events at or after this unix timestamp

Events come back newest first.

Status#

/v1/status merges two sources:

{
  "db":   { "onlineCount": 143, "totalAccounts": 5210, "totalCharacters": 12874, "totalGuilds": 233 },
  "live": { "t": 1754049312, "type": 4, "kind": "stats", "online": 143,
            "maps": [ { "map": 0, "count": 61 }, { "map": 2, "count": 24 } ] }
}

db comes from the game database and is null when no data source is configured. live is the most recent population gauge from the GameServer and is null until the first one arrives.

Rankings and Profiles#

/v1/rankings/{kind} returns an array of rows with the database column names as keys - Name, cLevel, Class, ResetCount, MasterResetCount, Kills, and so on depending on the ranking.

curl "http://your-host:55981/v1/rankings/resets"
curl "http://your-host:55981/v1/character/Kaelen"
curl "http://your-host:55981/v1/guild/Sahara"
curl "http://your-host:55981/v1/castle-siege"

Ranking lists are served from a cache rebuilt every MuDbRefreshSeconds, so they can be slightly stale. Character, guild and siege lookups are answered live.

Deployment Notes#

  • No authentication, no TLS, no rate limiting. BridgeServer is a plain HTTP server on purpose. Put it behind your existing reverse proxy if you want any of those, exactly as you would for any small internal service.
  • Do not point browsers straight at it. Read it from your website's backend or your bot, cache there, and serve your own visitors. That keeps subscriber slots free and stops the API being exposed publicly.
  • The API only reads. No endpoint can change game state, and BridgeServer never writes to the game database.

Common Issues#

  • 503 on rankings or profiles - the game database is not configured, or the first refresh has not completed
  • Stream connects, then goes quiet - no events are being exported; check the Bridge plugin config on the GameServer
  • Stream refused - every subscriber slot is taken; consolidate to one backend consumer
  • History empty after a restart - bridge.db was deleted, or nothing has been exported yet
  • Rankings are stale - expected within MuDbRefreshSeconds; lower it only if you accept the extra database load
  • Nothing responds at all - BridgeServer is not running, or HttpPort is blocked between you and it

Last updated October 1, 2026