{"openapi":"3.1.0","info":{"title":"Bedrock Server Info API","version":"2.0.0","description":"Live status, player counts and history for Minecraft Bedrock Edition servers. Every endpoint requires an API key of your own (`x-api-key`); create one at https://bedrockinfo.pages.dev/account. Both IPv4 and IPv6 servers are supported (`[2001:db8::1]:19132`). Responses are cached briefly (60 s for live status, 10 min for aggregates) — poll no faster than once a minute per server.","contact":{"name":"Bedrock Server Info","url":"https://bedrockinfo.com"}},"servers":[{"url":"https://api.bedrockinfo.com"},{"url":"https://bedrockinfo-api.bedrockinfo.workers.dev"}],"tags":[{"name":"Status","description":"Live server lookups."},{"name":"Network","description":"Aggregate and directory data."},{"name":"Account","description":"Key management (Supabase JWT, not an API key)."}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"x-api-key","description":"Your API key (starts with `bki_`)."},"supabaseToken":{"type":"apiKey","in":"header","name":"x-supabase-token","description":"Supabase access token from the signed-in dashboard session."}},"schemas":{"Error":{"type":"object","required":["message"],"properties":{"message":{"type":"string","description":"Human-readable explanation."},"code":{"type":"string","description":"Stable machine-readable error code.","enum":["invalid_parameter","missing_api_key","invalid_api_key","key_revoked","legacy_key_retired","key_limit_reached","origin_not_allowed","unauthorized","forbidden","not_found","bot_check_failed","bot_check_unavailable","quota_exceeded","rate_limited","stats_not_ready","database_unavailable","service_unavailable","internal_error"]}}},"ServerStatus":{"type":"object","description":"Live status of one Bedrock server. Keys are omitted when they do not apply, and `online` is `null` only if the check itself could not be made (which is answered as 503, not 200).","properties":{"host":{"type":"string","description":"The address as requested."},"port":{"type":"integer"},"checkedAt":{"type":"integer","description":"Unix seconds of this check."},"online":{"type":"boolean"},"name":{"type":"string","description":"MOTD with §-formatting codes stripped (capped at 128 chars)."},"players":{"type":"integer"},"maxPlayers":{"type":"integer"},"version":{"type":"string"},"protocol":{"type":"integer"},"levelName":{"type":"string"},"gameMode":{"type":"string"},"serverId":{"type":"integer","description":"World/server id reported by the server (rotates on proxies)."},"serverGuid":{"type":"integer","description":"RakNet GUID of the node that answered (rotates on proxies)."},"edition":{"type":"string","description":"Only present when it is not MCPE."},"ip":{"type":"string"},"ipVersion":{"type":"integer","enum":[4,6],"description":"Address family that answered."},"ipv6Port":{"type":"integer","description":"Only present when the server advertises a separate IPv6 port."},"lastSeen":{"type":"integer","nullable":true,"description":"Unix seconds this server was last up: now if it is up, otherwise the last successful check."},"geo":{"type":"object","properties":{"country":{"type":"string"},"countryCode":{"type":"string"},"city":{"type":"string"}}},"history":{"type":"array","description":"Player samples as [unixSeconds, players], ascending, at most one per hour; -1 players means the server was offline. A 30-day window returns at most 720 points.","items":{"type":"array","items":{"type":"integer"},"minItems":2,"maxItems":2}},"uptime":{"type":"number","description":"Uptime percentage across the returned window."},"degraded":{"type":"boolean","description":"History/geo are unavailable right now; the live status is real."}}},"TrackedServer":{"type":"object","properties":{"host":{"type":"string"},"port":{"type":"integer"},"name":{"type":"string","nullable":true},"online":{"type":"boolean","description":"Seen online within the last 3 hours."},"players":{"type":"integer","nullable":true,"description":"Players at the last scan."},"lastSeen":{"type":"integer","nullable":true},"uptimePercent":{"type":"number","nullable":true,"description":"Share of scans that found it online since tracking began."},"samples":{"type":"integer","description":"Scans recorded for this server."}}},"Preview":{"type":"object","properties":{"host":{"type":"string"},"port":{"type":"integer"},"online":{"type":"boolean","nullable":true,"description":"true = up, false = checked and not up, null = could not be checked (the pinger reported DNS/timeout). A null is not a claim that the server is offline."},"name":{"type":"string","nullable":true},"players":{"type":"integer","nullable":true},"maxPlayers":{"type":"integer","nullable":true}}}}},"paths":{"/v1/status":{"get":{"tags":["Retired"],"summary":"Retired — use /status","deprecated":true,"description":"Answers `410 gone`. The v1 shape was merged into the single status shape.","security":[{"apiKey":[]}],"responses":{"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"description":"Endpoint retired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Daily quota or per-IP rate limit exceeded. See `retry-after`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v2/status":{"get":{"tags":["Retired"],"summary":"Retired — use /status","deprecated":true,"description":"Answers `410 gone`. The v2 shape is now the only status shape, without the version prefix.","security":[{"apiKey":[]}],"responses":{"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"description":"Endpoint retired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Daily quota or per-IP rate limit exceeded. See `retry-after`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v2/preview":{"get":{"tags":["Retired"],"summary":"Retired — use /preview","deprecated":true,"description":"Answers `410 gone`. Preview moved to an unversioned path.","security":[{"apiKey":[]}],"responses":{"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"description":"Endpoint retired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Daily quota or per-IP rate limit exceeded. See `retry-after`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v2/featured":{"get":{"tags":["Retired"],"summary":"Retired — use /featured","deprecated":true,"description":"Answers `410 gone`. The featured list moved to an unversioned path.","security":[{"apiKey":[]}],"responses":{"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"description":"Endpoint retired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Daily quota or per-IP rate limit exceeded. See `retry-after`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v2/servers":{"get":{"tags":["Retired"],"summary":"Retired — use /servers","deprecated":true,"description":"Answers `410 gone`. The directory moved to an unversioned path.","security":[{"apiKey":[]}],"responses":{"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"description":"Endpoint retired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Daily quota or per-IP rate limit exceeded. See `retry-after`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v2/summary":{"get":{"tags":["Retired"],"summary":"Retired — use /servers","deprecated":true,"description":"Answers `410 gone`. The network aggregate was removed with the front-page chart.","security":[{"apiKey":[]}],"responses":{"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"description":"Endpoint retired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Daily quota or per-IP rate limit exceeded. See `retry-after`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/status":{"get":{"tags":["Status"],"summary":"Server status with history","description":"The live ping plus (optionally) player history, uptime and geo data. This is the ONE status shape: the old `/v1/status` and `/v2/status` shapes were merged into it, and both now answer `410`. **Coming from v1 or v2?** `Port`/`IPv4Port` -> `port`, `HostName` -> `host`, `CheckTimestamp`/`CheckTimeStamp` -> `checkedAt`, `Online` -> `online`, `ServerName` -> `name`, `Players` -> `players`, `MaxPlayers` -> `maxPlayers`, `Version` -> `version`, `Protocol` -> `protocol`, `LevelName` -> `levelName`, `GameMode` -> `gameMode`, `ServerId` -> `serverId`, `ServerGuid` -> `serverGuid`, `Edition` -> `edition`, `IPAddress` -> `ip`, `IPVersion` -> `ipVersion`, `LastSeen` -> `lastSeen`, `Uptime` -> `uptime`, `History` -> `history`, `Degraded` -> `degraded`, and `Geo.country_code` -> `geo.countryCode`. History is BUCKETED TO THE HOUR (at most one point per server per hour, `players = -1` while offline), so even a 30-day request returns at most 720 points. Samples are recorded by the background scanner roughly hourly — every 6 h for servers that have been offline for a while — and a server that was never scanned simply has no history yet. Uptime is time-weighted over the samples in the requested window.","security":[{"apiKey":[]}],"parameters":[{"name":"server","in":"query","required":true,"schema":{"type":"string"},"description":"`host`, `host:port`, `[ipv6]` or `[ipv6]:port`."},{"name":"history","in":"query","required":false,"schema":{"type":"integer","minimum":0,"maximum":30,"default":0},"description":"Days of history to include (0 = live status only)."}],"responses":{"200":{"description":"Status payload.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerStatus"}}}},"400":{"description":"Invalid parameters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Key revoked, origin not allowed, or the bot check failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Daily quota or per-IP rate limit exceeded. See `retry-after`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Temporarily unavailable (database limits or maintenance).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/preview":{"get":{"tags":["Network"],"summary":"Lightweight live status (single or batch)","description":"Live status only — no history, no geo. Use `?servers=a,b,c` (max 12) to check several servers in one request. Called by the website for its server cards; crawler-friendly (no bot check).","security":[{"apiKey":[]}],"parameters":[{"name":"server","in":"query","required":false,"schema":{"type":"string"}},{"name":"servers","in":"query","required":false,"schema":{"type":"string"},"description":"Comma-separated list, max 12 entries."}],"responses":{"200":{"description":"One preview object, or `{ \"previews\": [...] }` for the batch form.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Preview"}}}},"400":{"description":"Invalid parameters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Daily quota or per-IP rate limit exceeded. See `retry-after`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Temporarily unavailable (database limits or maintenance).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/featured":{"get":{"tags":["Network"],"summary":"Featured servers","description":"The curated server list shown on the website home page. Cached for 10 minutes.","security":[{"apiKey":[]}],"responses":{"200":{"description":"Featured list.","content":{"application/json":{"schema":{"type":"object","properties":{"featured":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"host":{"type":"string"},"port":{"type":"integer"}}}}}}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Daily quota or per-IP rate limit exceeded. See `retry-after`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/servers":{"get":{"tags":["Network"],"summary":"Tracked-server directory","description":"Paged list of every tracked server with its last player count and lifetime (time-weighted) uptime. Sort by `players`, `uptime`, `name` or `seen` — this doubles as the leaderboard.","security":[{"apiKey":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0}},{"name":"sort","in":"query","required":false,"schema":{"type":"string","enum":["players","uptime","name","seen"],"default":"players"}}],"responses":{"200":{"description":"A page of tracked servers.","content":{"application/json":{"schema":{"type":"object","properties":{"total":{"type":"integer"},"limit":{"type":"integer"},"offset":{"type":"integer"},"sort":{"type":"string"},"servers":{"type":"array","items":{"$ref":"#/components/schemas/TrackedServer"}}}}}}},"400":{"description":"Invalid parameters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Daily quota or per-IP rate limit exceeded. See `retry-after`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Temporarily unavailable (database limits or maintenance).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/badge/{address}.svg":{"get":{"tags":["Network"],"summary":"Embeddable SVG status badge","description":"A small SVG you can put on your own site, forum or README: server name, online state, players and uptime. No API key (it is a public image), cached for 5 minutes, IP rate limited. Embed it as `<img src=\"https://api.bedrockinfo.com/badge/play.example.com.svg\">`.","parameters":[{"name":"address","in":"path","required":true,"description":"`host`, `host:port`, `[ipv6]` or `[ipv6]:port`, with the `.svg` suffix.","schema":{"type":"string"}}],"responses":{"200":{"description":"SVG image.","content":{"image/svg+xml":{"schema":{"type":"string"}}}},"429":{"description":"Daily quota or per-IP rate limit exceeded. See `retry-after`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/account/keys":{"get":{"tags":["Account"],"summary":"List your API keys","security":[{"supabaseToken":[]}],"responses":{"200":{"description":"Keys with today’s usage."},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Account"],"summary":"Create an API key","description":"The raw key is returned once and never stored — save it immediately. Maximum 5 active keys per account.","security":[{"supabaseToken":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"label":{"type":"string","maxLength":64}}}}}},"responses":{"201":{"description":"Created (includes the raw key)."},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Key revoked, origin not allowed, or the bot check failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/account/keys/revoke":{"post":{"tags":["Account"],"summary":"Revoke one of your keys","security":[{"supabaseToken":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["key_hash"],"properties":{"key_hash":{"type":"string"}}}}}},"responses":{"200":{"description":"Revoked."},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/account/keys/{hash}/usage":{"get":{"tags":["Account"],"summary":"Usage history for one key","security":[{"supabaseToken":[]}],"parameters":[{"name":"hash","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Daily request counts plus the key record."},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"x-rate-limit-headers":[{"name":"x-ratelimit-limit","description":"Requests allowed in the current window.","schema":{"type":"integer"}},{"name":"x-ratelimit-remaining","description":"Requests left in the current window.","schema":{"type":"integer"}},{"name":"x-ratelimit-reset","description":"Unix seconds when the window resets.","schema":{"type":"integer"}},{"name":"retry-after","description":"Seconds to wait before retrying (429 responses).","schema":{"type":"integer"}}]}