Skip to content
Developers

Wardog Servers API

The data behind this site is available as a free, read-only JSON API: every WARDOGS server the in-game browser lists, refreshed about once a minute, plus player history for charts and per-server history by join code.

Overview

The base URL is https://api.wardogservers.com. Every response is JSON and reads need no API key.

The full contract is published as an OpenAPI 3.1 document at https://api.wardogservers.com/openapi.json ↗. That document is the source of truth; this page is the readable summary.

  • The collector polls the in-game server browser in every region about once a minute. The current interval is reported in every response as meta.refreshSeconds.
  • Browser requests are only accepted from wardogservers.com. Call the API from your own server, bot or script, not from a web page on another domain.
  • Unknown, duplicate or malformed query parameters are rejected with a 400 rather than ignored, so a typo fails loudly instead of silently returning everything.

Quick start

Three requests cover most uses: the totals right now, the busiest servers matching a filter, and one server by its instance id.

# Totals right now
curl https://api.wardogservers.com/v1/stats

# Community servers in Western Europe with room to join, busiest first
curl "https://api.wardogservers.com/v1/servers?region=eu-west&type=community&hideFull=true&limit=10"

# One server by instance id
curl https://api.wardogservers.com/v1/servers/$ID

A trimmed response from /v1/stats looks like this:

{
  "data": {
    "serverCount": 3544,
    "playerCount": 252109,
    "officialServerCount": 2765,
    "communityServerCount": 779
  },
  "meta": {
    "fetchedAt": "2026-09-13T12:54:57.302Z",
    "snapshotId": "95635cb938811a34…",
    "refreshSeconds": 60,
    "stale": false,
    "requestedRegions": ["asia-east", "asia-southeast", "…"],
    "observedRegions": ["asia-east", "asia-west", "…"]
  }
}

Endpoints

Every read endpoint accepts GET and HEAD, honours If-None-Match, and uses the response envelope described below.

EndpointReturns
GET /v1/serversThe server list with filters, sorting and pagination.
GET /v1/servers/{id}One server with the extra detail fields: raw attributes, build, level and cash limits.
GET /v1/snapshotEvery server in the current snapshot, ordered by instance id, with no filters or pagination. For clients that hold the whole list and filter locally.
GET /v1/regionsServer and player counts per region, and whether each region was requested and observed in the last poll.
GET /v1/statsTotals: servers and players online, split into official and community.
GET /v1/history/playersPlayer and server time series for charts.
GET /v1/history/heatmapAverage of a metric by UTC weekday and hour over the last 28 days.
GET /v1/history/peaksAll-time and daily player peaks, overall and per server type.
GET /v1/history/serversServers the collector has recorded, by join code, most recently seen first. Finds a server that is offline right now.
GET /v1/history/servers/rankingServers ranked by uptime times players: average players over the last day, week or month, with time offline counted as zero.
GET /v1/history/servers/{serverId}Players, peak, capacity, uptime share and map per bucket for one server, by join code.
GET /v1/history/servers/{serverId}/eventsChange log for one server, newest first: online, offline, restart, build, map, modifier and limit changes.
GET /readyz200 while a fresh snapshot is available, 503 when it is missing or stale.
GET /openapi.jsonThe OpenAPI 3.1 document.

Filtering the server list

Every parameter on /v1/servers is optional. Values are case-sensitive except q.

ParameterValuesMeaning
qtext, up to 200 charactersCase-insensitive search across instance id, join code, name, region, server number, map and mode.
regioneu-west,eu-centralOne or more region ids, comma-separated. /v1/regions has the current list.
typeall · official · community, default allOfficial servers, community servers, or both.
hideEmptytrue · falseDrop servers with zero players.
hideFulltrue · falseDrop servers at capacity, including 0/0.
passwordProtectedtrue · falseOnly locked or only open servers. Omit for both.
sort-players · players · name · region-players is busiest first and the default.
limit1–500, default 100Page size.
offset0–100000, default 0Zero-based offset into the filtered, sorted result.

Response envelope

Every successful response carries data and meta. The list endpoint adds pagination; history endpoints add a history object describing the window, group, metric and bucket size.

Check meta.stale before presenting numbers as current. It is true when collection has paused or the snapshot is older than refreshSeconds plus 60 seconds. Stale responses are also sent with Cache-Control: no-store and X-Data-Stale: true.

FieldMeaning
meta.fetchedAtWhen the collector fetched this snapshot.
meta.snapshotIdIdentifier of the snapshot; changes whenever new data lands. Also sent as the X-Snapshot-Id header.
meta.refreshSecondsHow often the collector polls. Do not poll faster than this.
meta.staleTrue when the data should not be presented as live.
meta.requestedRegionsRegions the collector asked the game for.
meta.observedRegionsRegions that returned at least one server in this snapshot.

The server object

This is the summary shape returned by /v1/servers and /v1/snapshot. /v1/servers/{id} returns the same fields plus attributes, build, level and cash.

{
  "id": "001ab36e-9985-4a9c-8dff-9dc137826dfe",
  "serverId": "756872",
  "serverIdUpdatedAt": "2026-09-13T11:14:32.995Z",
  "name": "Official #4",
  "nativeName": "f14859cce268",
  "type": "official",
  "region": "eu-south",
  "players": 100,
  "maxPlayers": 100,
  "reservedPlayers": 0,
  "passwordProtected": false,
  "serverNumber": 4,
  "map": { "variant": "Bakurani", "base": "Europe" },
  "mode": { "experience": "Bakurani_KOTH_01", "gameMode": "GM_Session_C", "modifiers": [] },
  "rulesets": ["standard"]
}
FieldMeaning
idLive game instance id. It changes when the server restarts, so do not use it as a permanent key.
serverIdIn-game join code as a string: a decimal number with leading zeros kept for official servers, or a UUID for community servers since game patch 0.11. Null when not yet collected.
serverIdUpdatedAtWhen the join code was last looked up. Null whenever serverId is null.
nameOfficial #N for official servers, otherwise the decoded server name. May be empty.
nativeNameThe raw server name as the game reports it.
typeofficial or community.
regionRegion id, for example eu-west.
players · maxPlayers · reservedPlayersCurrent, maximum and reserved player counts.
passwordProtectedWhether a password is required to join.
serverNumberThe official server number; 0 for community servers.
mapMap variant and base map. Either may be null.
modeExperience and game mode names plus the raw + modifiers from the experience string, in source order.
rulesetsstandard, hardcore or infantry-only, derived from the modifiers. Exactly ["standard"] when none is recognised.

History

/v1/history/players returns a columnar series: one array of bucket start times in Unix seconds and one array of values per group key, aligned by index. A null value is a bucket where nothing was recorded, usually because the collector was paused. Show a gap; do not interpolate.

Buckets are recorded once per snapshot from the moment history was switched on, with no backfill. Minute buckets are kept for 7 days, five-minute buckets for 90 days and hourly buckets indefinitely.

ParameterValuesMeaning
window24h · 7d · 30d · 90d · 1y, default 24hWindow ending now. The bucket step is 60 s for 24h, 300 s for 7d, 3600 s for 30d and 90d, and 21600 s for 1y.
grouptotal · type · region · regionType · map · ruleset, default totalOne series per key: total; official and community; region; region|type; map variant; or primary ruleset.
metricplayers · servers · full · empty · capacity · free, default playersplayers, servers, full (at capacity), empty, capacity (sum of maxPlayers) or free (capacity minus players). Each value is the average over the snapshots in the bucket.
GET /v1/history/players?window=24h&group=type

{
  "data": {
    "t": [1789217880, 1789217940, 1789218000, "…"],
    "series": {
      "official": [248112, 248390, null, "…"],
      "community": [3902, 3911, null, "…"]
    },
    "peak": { "official": ["…"], "community": ["…"] }
  },
  "history": {
    "window": "24h", "group": "type", "metric": "players",
    "step": 60, "from": 1789217880, "to": 1789304280, "since": 1789257600
  },
  "meta": { "…": "…" }
}

For metric=players the response also carries a peak object with the highest single snapshot in each bucket. The heatmap endpoint averages a metric by UTC weekday and hour over the last 28 whole days, optionally for one region. Peaks reports the highest single-snapshot player count overall and per type, all time and for each of the last 30 UTC days.

Per-server history

Per-server history is keyed by the join code, which persists across restarts and map changes, unlike the instance id. /v1/history/servers/{serverId} returns aligned arrays: average players and the highest single poll while the server was listed, its player cap, the share of polls in the bucket that listed it (its uptime, 0 to 1) and the map at the end of the bucket, so consecutive map values give the rotation. Recording starts the first time a code is seen, with no backfill, and the route answers 404 until then.

ParameterValuesMeaning
window24h · 7d · 30d · 90d, default 24h24h and 7d use five-minute buckets kept for 7 days; 30d and 90d use hourly buckets kept for 90 days.
GET /v1/history/servers/756872?window=24h

{
  "data": {
    "server": {
      "serverId": "756872",
      "name": "Official #4",
      "type": "official",
      "region": "eu-south",
      "instanceId": "001ab36e-9985-4a9c-8dff-9dc137826dfe",
      "firstSeen": "2026-09-13T00:00:39.000Z",
      "lastSeen": "2026-09-14T11:53:22.000Z",
      "samples": 2132,
      "live": true
    },
    "t": [1789300800, 1789301100, 1789301400, "…"],
    "players": [100, 98, null, "…"],
    "peak": [100, 100, null, "…"],
    "capacity": [100, 100, null, "…"],
    "online": [1, 0.8, null, "…"],
    "map": ["Bakurani", "Bakurani", null, "…"]
  },
  "history": {
    "window": "24h", "step": 300,
    "from": 1789300800, "to": 1789387200, "since": 1789257600
  },
  "meta": { "…": "…" }
}

/v1/history/servers/{serverId}/events derives a change log from consecutive polls, newest first: tracking_started, online, offline, restart (the instance changed with no gap), build (changelist), map (variant, world or sky), modifiers and limits (level and cash ranges, password and player cap). Each event carries at, kind, and before and after objects holding only the fields that define that kind, null where there is no earlier or later state. limit takes 1 to 500 and defaults to 100.

GET /v1/history/servers/756872/events?limit=3

{
  "data": {
    "server": { "serverId": "756872", "name": "Official #4", "…": "…" },
    "events": [
      {
        "at": "2026-09-14T12:40:30.000Z",
        "kind": "map",
        "before": { "map": "Madrid", "world": "Europe", "sky": "DayEarlyFog" },
        "after": { "map": "Detroit", "world": "Europe", "sky": "DayLateGray" }
      },
      {
        "at": "2026-09-14T09:12:04.000Z",
        "kind": "restart",
        "before": { "instanceId": "001ab36e-9985-4a9c-8dff-9dc137826dfe" },
        "after": { "instanceId": "7c1e0b52-3d9a-4e1f-9b6e-2f4c8a9d0e11" }
      },
      {
        "at": "2026-09-13T00:00:39.000Z",
        "kind": "tracking_started",
        "before": null,
        "after": { "instanceId": "…", "changelist": "501228", "map": "Bakurani", "…": "…" }
      }
    ]
  },
  "history": { "limit": 3, "since": 1789257600 },
  "meta": { "…": "…" }
}

/v1/history/servers lists recorded servers, most recently seen first. q matches a join code exactly or a last known name case-insensitively, so it finds a server that is offline right now; live servers already carry their serverId in /v1/servers.

ParameterValuesMeaning
qtext, up to 200 charactersA join code, matched exactly, or a last known name, matched case-insensitively.
typeall · official · community, default allOfficial servers, community servers, or both.
limit1–100, default 20How many servers to return.

Caching and polling

Every response carries an ETag. Send it back as If-None-Match and an unchanged snapshot answers 304 with no body, which costs almost nothing on either side. A once-a-minute poll with If-None-Match is the intended pattern.

const res = await fetch('https://api.wardogservers.com/v1/snapshot', {
  headers: lastEtag ? { 'if-none-match': lastEtag } : {}
});
if (res.status === 304) return; // Nothing changed since the last poll
lastEtag = res.headers.get('etag');
const { data, meta } = await res.json();
if (meta.stale) console.warn('stale'); // Collector is behind; keep showing the last known data

Fresh responses are cached at the edge for 15 seconds, so bursts of identical requests are cheap. There is no hard rate limit today, but polling faster than meta.refreshSeconds cannot return newer data and may be throttled in future. If you need the whole list, fetch /v1/snapshot once and filter locally rather than paging through /v1/servers.

Errors

Errors are JSON with a stable code and a human-readable message. The message may change; match on the code.

{
  "error": {
    "code": "bad_request",
    "message": "An unsupported query parameter was supplied."
  }
}
StatusWhen
400Unknown, duplicate or invalid query parameter, or an id that is not a UUID.
401An API key was required and was missing or wrong. Not returned for reads while public access is on.
404No server with that id in the current snapshot, no history for that join code, or an unknown route.
405A method other than GET or HEAD.
503No usable snapshot. Retry after a minute or when /readyz returns 200.

Terms of use

The API is free for community projects: Discord bots, overlays, dashboards, research. Credit Wardog Servers with a link where you show the data publicly, and do not present it as official BULKHEAD or Team17 data.

The read API is versioned under /v1. New fields may be added within a version, so tolerate keys you do not recognise. Breaking changes will ship as a new version and be announced on this page first.

Questions, a project to show off, or a field you need: Discord @xcausxn.