{
  "openapi": "3.1.0",
  "info": {
    "title": "WARDOGS Server List API",
    "version": "1.0.0",
    "description": "Public, read-only API for live WARDOGS server data — per-server lookup, community uptime leaderboard, player-count history, and embeddable status banners.\n\n**Bulk listing is intentionally not offered.** These endpoints let you track specific servers, surface the community leaderboard, and show aggregate stats — but not enumerate the whole server browser. `/api/server` requires a `key`, `code`, or `id` you already have (from the leaderboard, the /embed builder, or a server's page on wardogserverlist.com).\n\n**No authentication.** All endpoints are GET, CORS is open (`Access-Control-Allow-Origin: *`), and responses are cached ~1–10 min. Fair-use rate limit: **60 requests / 60s per IP** (HTTP 429 with `Retry-After` when exceeded). Please cache on your side and poll no faster than once every few minutes — the underlying data only changes every ~10 minutes (we poll Bulkhead's backend gently).\n\nGreat for Discord bots, dashboards, and status pages. Community server identity (`serverKey`) is derived from the server name and is stable across restarts; `joinCode` is the in-game join code (community servers).",
    "contact": { "name": "wardogserverlist.com", "url": "https://wardogserverlist.com" }
  },
  "servers": [{ "url": "https://wardogserverlist.com", "description": "Production" }],
  "tags": [
    { "name": "Servers", "description": "Live server list and lookup" },
    { "name": "Stats", "description": "Aggregate and historical statistics" },
    { "name": "Embed", "description": "Embeddable banner image" }
  ],
  "paths": {
    "/api/server": {
      "get": {
        "tags": ["Servers"], "summary": "Look up one server",
        "description": "Returns a single server by stable `key` (recommended), current `code` (in-game join code), or `id` (per-session instance id). Provide exactly one.",
        "operationId": "getServer",
        "parameters": [
          { "name": "key", "in": "query", "required": false, "schema": { "type": "string" }, "description": "Stable serverKey, e.g. `eu-west|my-server`.", "example": "eu-west|my-server" },
          { "name": "code", "in": "query", "required": false, "schema": { "type": "string" }, "description": "In-game join code (community).", "example": "740878" },
          { "name": "id", "in": "query", "required": false, "schema": { "type": "string" }, "description": "gameInstanceId (changes on restart)." }
        ],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "server": { "$ref": "#/components/schemas/Server" }, "updatedAt": { "type": "string", "format": "date-time" } } } } } },
          "404": { "description": "Server not found" }
        }
      }
    },
    "/api/leaderboard": {
      "get": {
        "tags": ["Stats"], "summary": "Community uptime/population leaderboard",
        "description": "Top community servers over a 7-day window, ranked by score = avg players × uptime.",
        "operationId": "getLeaderboard",
        "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LeaderboardResponse" } } } } }
      }
    },
    "/api/server-history": {
      "get": {
        "tags": ["Stats"], "summary": "Per-server history (players, rank, uptime)",
        "operationId": "getServerHistory",
        "parameters": [{ "name": "key", "in": "query", "required": true, "schema": { "type": "string" }, "description": "Stable serverKey." }],
        "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ServerHistory" } } } } }
      }
    },
    "/api/server-detail": {
      "get": {
        "tags": ["Stats"], "summary": "Server page data (live info + history)",
        "description": "Everything shown on a server's page: the live entry (if online), player history, daily uptime, rank history, map time-share and the map/modifier/restart change log. Community servers only have history; official fleet servers return `tracked: false` with the live entry. Map/modifier history is recorded from September 2026 onward.",
        "operationId": "getServerDetail",
        "parameters": [
          { "name": "key", "in": "query", "required": false, "schema": { "type": "string" }, "description": "Stable serverKey (community)." },
          { "name": "id", "in": "query", "required": false, "schema": { "type": "string" }, "description": "gameInstanceId (use for official servers)." },
          { "name": "window", "in": "query", "schema": { "type": "string", "enum": ["24h", "7d", "30d"], "default": "7d" } }
        ],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ServerDetail" } } } },
          "404": { "description": "Server not found" }
        }
      }
    },
    "/api/builds": {
      "get": {
        "tags": ["Stats"], "summary": "Game builds (update tracker)",
        "description": "Every game build (changelist + build configuration) seen across all servers, newest first, with when it was first and last seen and how many servers ran it. `current` lists the builds running at the latest poll with their share of servers — a new entry there means an update is rolling out. Recorded from September 2026.",
        "operationId": "getBuilds",
        "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BuildsResponse" } } } } }
      }
    },
    "/api/totals": {
      "get": {
        "tags": ["Stats"], "summary": "Global player/server totals over time",
        "operationId": "getTotals",
        "parameters": [{ "name": "window", "in": "query", "schema": { "type": "string", "enum": ["24h", "7d", "30d"], "default": "7d" } }],
        "responses": { "200": { "description": "Downsampled time-series", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TotalsResponse" } } } } }
      }
    },
    "/api/region-history": {
      "get": {
        "tags": ["Stats"], "summary": "Per-region player totals over time",
        "operationId": "getRegionHistory",
        "parameters": [{ "name": "window", "in": "query", "schema": { "type": "string", "enum": ["24h", "7d", "30d"], "default": "7d" } }],
        "responses": { "200": { "description": "Per-region time-series", "content": { "application/json": { "schema": { "type": "object", "properties": { "window": { "type": "string" }, "series": { "type": "object", "additionalProperties": { "type": "array", "items": { "type": "array", "items": { "type": "number" }, "minItems": 2, "maxItems": 2 } } } } } } } } }
      }
    },
    "/banner.png": {
      "get": {
        "tags": ["Embed"], "summary": "Live server status banner (PNG)",
        "description": "The same banner as `/banner.svg`, rendered as a 936×192 PNG (936×96 compact) for places that don't display SVG, such as Discord. Cached ~60s. Uncached lookups are limited to 120 per 60s per IP (HTTP 429 with `Retry-After`); parameters over 200 characters return 400.",
        "operationId": "getBannerPng",
        "parameters": [
          { "name": "key", "in": "query", "schema": { "type": "string" }, "description": "Stable serverKey (recommended)." },
          { "name": "code", "in": "query", "schema": { "type": "string" }, "description": "In-game join code." },
          { "name": "id", "in": "query", "schema": { "type": "string" }, "description": "gameInstanceId." },
          { "name": "style", "in": "query", "schema": { "type": "string", "enum": ["card", "compact"], "default": "card" } }
        ],
        "responses": { "200": { "description": "PNG image", "content": { "image/png": { "schema": { "type": "string", "format": "binary" } } } } }
      }
    },
    "/banner.svg": {
      "get": {
        "tags": ["Embed"], "summary": "Live server status banner (SVG)",
        "description": "An SVG banner for embedding on a website (`<img src=\"…/banner.svg?key=…\">`). Updates itself; cached ~60s. Uncached lookups are limited to 120 per 60s per IP (HTTP 429 with `Retry-After`); parameters over 200 characters return 400. See /embed for a copy-paste builder.",
        "operationId": "getBanner",
        "parameters": [
          { "name": "key", "in": "query", "schema": { "type": "string" }, "description": "Stable serverKey (recommended)." },
          { "name": "code", "in": "query", "schema": { "type": "string" }, "description": "In-game join code." },
          { "name": "id", "in": "query", "schema": { "type": "string" }, "description": "gameInstanceId." },
          { "name": "style", "in": "query", "schema": { "type": "string", "enum": ["card", "compact"], "default": "card" } }
        ],
        "responses": { "200": { "description": "SVG image", "content": { "image/svg+xml": { "schema": { "type": "string" } } } } }
      }
    }
  },
  "components": {
    "schemas": {
      "Server": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "description": "gameInstanceId (per-session; changes on restart)." },
          "serverKey": { "type": "string", "description": "Stable identity (region|name-slug)." },
          "name": { "type": "string" },
          "region": { "type": "string", "example": "eu-west" },
          "official": { "type": "boolean" },
          "joinCode": { "type": ["string", "null"], "description": "In-game join code (community servers)." },
          "passworded": { "type": "boolean" },
          "players": { "type": "integer" },
          "maxPlayers": { "type": "integer" },
          "map": { "type": "string" },
          "mode": { "type": "string" },
          "tags": { "type": "array", "items": { "type": "string" } },
          "imageUrl": { "type": "string" },
          "world": { "type": "string", "description": "World / rotation name (MAPNAME), e.g. `Europe`." },
          "gameMode": { "type": "string", "example": "KOTH" },
          "ruleset": { "type": "string", "description": "Community servers; `Default` = standard rules." },
          "matchStart": { "type": ["integer", "null"], "description": "Current match start (epoch ms)." },
          "cl": { "type": ["integer", "null"], "description": "Game build changelist." },
          "buildConfig": { "type": "string", "example": "Shipping" },
          "compatibleCl": { "type": ["integer", "null"] },
          "serverNumber": { "type": ["integer", "null"] },
          "reservedSlots": { "type": "integer" },
          "limits": { "type": ["object", "null"], "description": "Join limits; null = none. A max of 0 means no upper limit.", "properties": { "level": { "type": "array", "items": { "type": "integer" }, "minItems": 2, "maxItems": 2 }, "cash": { "type": "array", "items": { "type": "integer" }, "minItems": 2, "maxItems": 2 } } }
        }
      },
      "LeaderboardResponse": {
        "type": "object",
        "properties": {
          "windowDays": { "type": "integer" }, "totalPolls": { "type": "integer" }, "updatedAt": { "type": "string", "format": "date-time" },
          "servers": { "type": "array", "items": { "$ref": "#/components/schemas/LeaderboardEntry" } }
        }
      },
      "LeaderboardEntry": {
        "type": "object",
        "properties": {
          "serverKey": { "type": "string" }, "name": { "type": "string" }, "region": { "type": "string" },
          "score": { "type": "number", "description": "avg players × uptime" },
          "uptimePct": { "type": "number" }, "avgPlayers": { "type": "number" }, "peakPlayers": { "type": "integer" }, "polls": { "type": "integer" }
        }
      },
      "ServerHistory": {
        "type": "object",
        "properties": {
          "key": { "type": "string" },
          "players": { "type": "array", "items": { "type": "array", "items": { "type": "number" }, "minItems": 2, "maxItems": 2 }, "description": "[[epochMs, players], …]" },
          "rank": { "type": "array", "items": { "type": "array", "items": { "type": "number" }, "minItems": 2, "maxItems": 2 } },
          "uptime": { "type": "array", "items": { "type": "array", "items": { "type": "number" }, "minItems": 2, "maxItems": 2 } }
        }
      },
      "ServerDetail": {
        "type": "object",
        "properties": {
          "key": { "type": "string" }, "window": { "type": "string" }, "updatedAt": { "type": ["string", "null"], "format": "date-time" },
          "tracked": { "type": "boolean", "description": "false for official fleet servers (live info only)." },
          "official": { "type": "boolean" }, "name": { "type": ["string", "null"] }, "region": { "type": ["string", "null"] },
          "live": { "oneOf": [{ "$ref": "#/components/schemas/Server" }, { "type": "null" }], "description": "Current entry, or null when offline." },
          "firstSeen": { "type": ["integer", "null"], "description": "Earliest observation (epoch ms, within 30-day retention)." },
          "lastSeen": { "type": ["integer", "null"] },
          "session": { "type": ["object", "null"], "properties": { "id": { "type": "string" }, "since": { "type": "integer" }, "exact": { "type": "boolean", "description": "false = running at least since `since`." } } },
          "leaderboard": { "type": ["object", "null"], "properties": { "rank": { "type": "integer" }, "score": { "type": "number" }, "uptimePct": { "type": "number" }, "avgPlayers": { "type": "number" }, "peakPlayers": { "type": "integer" }, "windowDays": { "type": "integer" } } },
          "summary": { "type": "object", "properties": { "avgPlayers": { "type": "number" }, "peakPlayers": { "type": "integer" }, "uptimePct": { "type": "number" }, "polls": { "type": "integer" }, "totalPolls": { "type": "integer" } } },
          "bestRank": { "type": ["integer", "null"] },
          "players": { "type": "array", "items": { "type": "array", "items": { "type": "number" }, "minItems": 2, "maxItems": 2 }, "description": "[[epochMs, players], …]" },
          "dailyUptime": { "type": "array", "items": { "type": "array", "items": { "type": "number" }, "minItems": 2, "maxItems": 2 }, "description": "[[utcDayEpochMs, uptimePct], …]" },
          "rank": { "type": "array", "items": { "type": "array", "items": { "type": "number" }, "minItems": 2, "maxItems": 2 }, "description": "[[epochMs, rank], …] while in the top 100." },
          "maps": { "type": "array", "items": { "type": "object", "properties": { "map": { "type": "string" }, "polls": { "type": "integer" }, "pct": { "type": "number" } } } },
          "changes": { "type": "array", "description": "Newest first.", "items": { "type": "object", "properties": {
            "ts": { "type": "integer" }, "instance": { "type": "string" }, "map": { "type": "string" }, "sky": { "type": "string" },
            "tags": { "type": "array", "items": { "type": "string" } }, "first": { "type": "boolean" }, "restart": { "type": "boolean" }, "mapChanged": { "type": "boolean" },
            "added": { "type": "array", "items": { "type": "string" } }, "removed": { "type": "array", "items": { "type": "string" } },
            "world": { "type": "string" }, "cl": { "type": ["integer", "null"] }, "prevCl": { "type": ["integer", "null"] }, "buildChanged": { "type": "boolean" },
            "limits": { "type": "string", "description": "`minLevel-maxLevel|minCash-maxCash`, '' = none." }, "limitsChanged": { "type": "boolean" } } } }
        }
      },
      "Build": {
        "type": "object",
        "properties": {
          "cl": { "type": "integer" }, "config": { "type": "string" }, "compatibleCl": { "type": ["integer", "null"] },
          "firstSeen": { "type": "integer", "description": "epoch ms" }, "lastSeen": { "type": "integer" },
          "servers": { "type": "integer", "description": "Servers on this build at lastSeen." }, "official": { "type": "integer" },
          "peakServers": { "type": "integer" }, "current": { "type": "boolean" },
          "sharePct": { "type": "number", "description": "Only on `current` entries." }
        }
      },
      "BuildsResponse": {
        "type": "object",
        "properties": {
          "updatedAt": { "type": ["string", "null"], "format": "date-time" },
          "current": { "type": "array", "items": { "$ref": "#/components/schemas/Build" } },
          "history": { "type": "array", "items": { "$ref": "#/components/schemas/Build" } }
        }
      },
      "TotalsResponse": {
        "type": "object",
        "properties": {
          "window": { "type": "string" },
          "cols": { "type": "array", "items": { "type": "string" }, "example": ["ts", "offPlayers", "comPlayers", "offServers", "comServers", "offCapacity", "comCapacity"] },
          "rows": { "type": "array", "items": { "type": "array", "items": { "type": "number" } }, "description": "Each row aligns to `cols`." }
        }
      }
    }
  }
}
