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.
| Endpoint | Returns |
|---|---|
GET /v1/servers | The 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/snapshot | Every 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/regions | Server and player counts per region, and whether each region was requested and observed in the last poll. |
GET /v1/stats | Totals: servers and players online, split into official and community. |
GET /v1/history/players | Player and server time series for charts. |
GET /v1/history/heatmap | Average of a metric by UTC weekday and hour over the last 28 days. |
GET /v1/history/peaks | All-time and daily player peaks, overall and per server type. |
GET /v1/history/servers | Servers the collector has recorded, by join code, most recently seen first. Finds a server that is offline right now. |
GET /v1/history/servers/ranking | Servers 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}/events | Change log for one server, newest first: online, offline, restart, build, map, modifier and limit changes. |
GET /readyz | 200 while a fresh snapshot is available, 503 when it is missing or stale. |
GET /openapi.json | The OpenAPI 3.1 document. |
Filtering the server list
Every parameter on /v1/servers is optional. Values are case-sensitive except q.
| Parameter | Values | Meaning |
|---|---|---|
q | text, up to 200 characters | Case-insensitive search across instance id, join code, name, region, server number, map and mode. |
region | eu-west,eu-central | One or more region ids, comma-separated. /v1/regions has the current list. |
type | all · official · community, default all | Official servers, community servers, or both. |
hideEmpty | true · false | Drop servers with zero players. |
hideFull | true · false | Drop servers at capacity, including 0/0. |
passwordProtected | true · false | Only locked or only open servers. Omit for both. |
sort | -players · players · name · region | -players is busiest first and the default. |
limit | 1–500, default 100 | Page size. |
offset | 0–100000, default 0 | Zero-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.
| Field | Meaning |
|---|---|
meta.fetchedAt | When the collector fetched this snapshot. |
meta.snapshotId | Identifier of the snapshot; changes whenever new data lands. Also sent as the X-Snapshot-Id header. |
meta.refreshSeconds | How often the collector polls. Do not poll faster than this. |
meta.stale | True when the data should not be presented as live. |
meta.requestedRegions | Regions the collector asked the game for. |
meta.observedRegions | Regions 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"]
} | Field | Meaning |
|---|---|
id | Live game instance id. It changes when the server restarts, so do not use it as a permanent key. |
serverId | In-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. |
serverIdUpdatedAt | When the join code was last looked up. Null whenever serverId is null. |
name | Official #N for official servers, otherwise the decoded server name. May be empty. |
nativeName | The raw server name as the game reports it. |
type | official or community. |
region | Region id, for example eu-west. |
players · maxPlayers · reservedPlayers | Current, maximum and reserved player counts. |
passwordProtected | Whether a password is required to join. |
serverNumber | The official server number; 0 for community servers. |
map | Map variant and base map. Either may be null. |
mode | Experience and game mode names plus the raw + modifiers from the experience string, in source order. |
rulesets | standard, 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.
| Parameter | Values | Meaning |
|---|---|---|
window | 24h · 7d · 30d · 90d · 1y, default 24h | Window 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. |
group | total · type · region · regionType · map · ruleset, default total | One series per key: total; official and community; region; region|type; map variant; or primary ruleset. |
metric | players · servers · full · empty · capacity · free, default players | players, 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.
| Parameter | Values | Meaning |
|---|---|---|
window | 24h · 7d · 30d · 90d, default 24h | 24h 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.
| Parameter | Values | Meaning |
|---|---|---|
q | text, up to 200 characters | A join code, matched exactly, or a last known name, matched case-insensitively. |
type | all · official · community, default all | Official servers, community servers, or both. |
limit | 1–100, default 20 | How 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."
}
} | Status | When |
|---|---|
400 | Unknown, duplicate or invalid query parameter, or an id that is not a UUID. |
401 | An API key was required and was missing or wrong. Not returned for reads while public access is on. |
404 | No server with that id in the current snapshot, no history for that join code, or an unknown route. |
405 | A method other than GET or HEAD. |
503 | No 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.