API
Everything on this page is public and needs no account. Base URL for every endpoint ishttps://api.spawnlist.gg unless stated otherwise. Responses are JSON except where noted.
Basics
No versioned auth scheme, no OAuth, no rate-limit headers. The read endpoints are open and cached at the edge; the vote endpoints are authenticated by a per-server key you generate yourself. CORS is open for reads, so you can call them from a browser.
Errors come back as {"error":"..."} with a matching HTTP status. The one deliberate exception is the vote API, which answers in plain text — see below for why.
Vote rewards
This is the endpoint your reward plugin talks to. It deliberately copies the shape the older server lists settled on, so plugins like EasyVote and most uMod reward plugins work by changing the URL in their config and nothing else. That's why it returns a bare character instead of JSON, and why the key travels in the query string: it's their contract, not ours.
Get your key from your server's edit page under Vote rewards API. Treat it like a password — anyone holding it can mark votes as claimed. Rotating it invalidates the old one immediately.
Check a vote
A pure read. Ask as often as you like; it never consumes anything.
GET https://api.spawnlist.gg/api/?object=votes&element=claim&key=YOUR_KEY&steamid=76561198000000000Claim the reward
Returns 1 exactly once and 2 from then on. This is the call that pays out, so trigger your reward on it.
GET https://api.spawnlist.gg/api/?object=plugin&element=reward&key=YOUR_KEY&steamid=76561198000000000If you'd rather keep checking and claiming separate, POST to the first URL with&action=post and it claims instead of reading.
Responses
| Body | Meaning |
|---|---|
| 0 | No vote found in the last 24 hours |
| 1 | Voted, reward not claimed yet |
| 2 | Voted, reward already claimed |
Parameters
| Name | Required | Notes |
|---|---|---|
| key | yes | Your server key |
| steamid | one of | SteamID64, 17 digits. For Rust, Palworld, Valheim |
| username | one of | Player name, up to 32 chars. For Minecraft |
discordid is accepted by the sites we mirror but not by us — we don't collect a Discord identity on votes, so it would always answer 0.
Behaviour worth knowing before you go live
- 24-hour window. A vote from three days ago returns
0. - Claiming is safe under concurrency. Two simultaneous calls for the same vote — a retry, or two servers sharing a key — result in exactly one
1. You cannot double-pay. - Fraud-flagged votes never pay out. If our anti-fraud pass excludes a vote, this API reports
0for it. - An unknown key returns
0, not an error. That's what the plugins expect and it stops anyone probing for valid keys. The practical consequence: if your plugin suddenly reports nobody voting, check the key was pasted whole before assuming votes stopped. - Never cached. Every response is computed fresh — a cached
1could be spent twice.
For Minecraft: Java you probably want Votifierinstead, which pushes to your server the moment the vote lands. Full walkthrough in thevote rewards guide.
Discord vote webhook
Outbound, not inbound: you give us a webhook URL on your server's edit page and wePOST to it each time someone votes. Opt-in, and disabled unless you set it.
Only Discord webhook URLs are accepted. The URL must matchhttps://discord.com/api/webhooks/<id>/<token> (or the legacydiscordapp.com host). This is an anti-SSRF measure — an arbitrary URL would let anyone use us to fire requests at hosts they don't control — so you can't currently point this at your own backend. If you need that, say so and we'll look at signed generic webhooks.
What we send:
POST https://discord.com/api/webhooks/<id>/<token>
Content-Type: application/json
{
"embeds": [
{
"title": "🗳️ New vote for Your Server",
"description": "**Notch** just voted — **42** votes this month.",
"url": "https://spawnlist.gg/servers/your-server",
"color": 2257915,
"footer": { "text": "spawnlist.gg — where players spawn next" }
}
]
}- Best-effort, always. 5-second timeout, failures swallowed, fired outside the response path. A broken or deleted webhook can never affect a vote or slow the voter down — and we won't retry it.
- The name is the voter's, when we have one. Minecraft votes carry a username; Steam votes carry a SteamID we don't put in the message, so those read "Someone just voted".
- No signature header — the secret is the webhook URL itself, which is how Discord works.
Reading data
Open, CORS-enabled, cached at the edge. The cache TTL is in each response'sCache-Control, and it's the honest number — don't poll faster, you'll get the same bytes.
GET /v1/servers
Live listings. Cached 60s.
curl "https://api.spawnlist.gg/v1/servers?game=minecraft_java&tags=survival,pvp&sort=players"| Param | Values |
|---|---|
| game | minecraft_java · minecraft_bedrock · hytale · palworld · rust · valheim |
| q | Full-text search over name and description |
| sort | top (default) · new · rated · players |
| tags | Comma-separated, AND semantics. Max 5. tag is accepted as an alias |
| country | ISO 3166-1 alpha-2, e.g. US |
| region | Continent slug, e.g. europe, n-america |
| version | Substring match against the reported version |
| featured | 1 for paid placements only. Pair with placement=home|game |
Paging
Returns a plain array. X-Total-Count carries the full number of matches for your filters, which is the only way to tell a full page from the last one — it's exposed through CORS, so a browser can read it.
| Param | Default | Notes |
|---|---|---|
| limit | 36 | Max 100 per request |
| offset | 0 | No upper bound — iterate to walk the whole directory |
# every Minecraft: Java server, 100 at a time
offset=0
while :; do
page=$(curl -s "https://api.spawnlist.gg/v1/servers?game=minecraft_java&limit=100&offset=$offset")
[ "$(echo "$page" | jq length)" -eq 0 ] && break
echo "$page" | jq -c '.[]'
offset=$((offset + 100))
doneOne exception: sort=players orders by the live player count, which lives in KV and not in the database, so it can't page in SQL. It pages inside a pool of the top 250 by score and reports that pool as the total. Every other sort pages over the full set.
GET /v1/servers/:slug
One listing, same object shape as the array above plus announcement, screenshots and unclaimed. Cached 60s. 404 if the slug isn't live.
{
"id": "c9256956-3e27-465f-9b0b-0fefea92aa39",
"slug": "hypixel",
"name": "Hypixel",
"game": "minecraft_java",
"tags": ["minigames", "skyblock"],
"ip": "mc.hypixel.net",
"version": "1.8.x-1.21.x",
"country": "US",
"regions": ["US", "DE"],
"players": 37303,
"maxPlayers": 200000,
"votes": 0,
"rank": 1,
"rating": null,
"ratingCount": 0,
"addedDays": 5,
"uptime": 100,
"featured": false,
"premium": false,
"bannerUrl": null,
"iconUrl": "https://api.spawnlist.gg/media/..."
}players is the live count from our pinger, not a stored snapshot.uptime is a percentage measured over 25 days, or null before we have enough data.
GET /v1/servers/:slug/metrics
Time series. days holds 30 days of uptime and player averages;hours holds a 24-slot average by UTC hour; hourlyis the raw per-(UTC day, UTC hour) matrix. Cached 300s.
If you render an hour-of-day chart, aggregate hourly in your viewer's timezone rather than shifting the hours aggregate — the offset changes with daylight saving, so shifting a finished average smears the peak across two hours.hasHourly is false until at least 7 distinct days exist, which is our way of not presenting one day of noise as a weekly pattern.
Other read endpoints
| Endpoint | Returns | Cache |
|---|---|---|
| GET /v1/games | Catalogue with live counts and players per game | 60s |
| GET /v1/tags/top | Most-used tags with counts | 60s |
| GET /v1/tags/suggest?q= | Tag autocomplete | 60s |
| GET /v1/trending | Servers above their weekly average right now | 120s |
| GET /v1/servers/:slug/reviews | {aggregate, reviews[], totalReviews} — 30 per page, limit (max 100) and offset | — |
| GET /v1/pricing | Featured slot and Premium prices in cents | 3600s |
| GET /health | {"ok":true} | — |
Badges
Live images you can embed anywhere. They show players online, votes and uptime, and update themselves. Cached 300s, CORS open, X-Robots-Tag: noindex.
https://api.spawnlist.gg/badge/<slug>.png PNG, 300×72, rendered at 2x
https://api.spawnlist.gg/badge/<slug>.png?style=light
https://api.spawnlist.gg/badge/<slug>.png?style=blue
https://spawnlist.gg/badge/<slug>.svg SVG, crisper, but blocked by some forumsUse the PNG for forums and Discord — many boards strip SVG from [img] because an SVG can carry script, and Discord embeds don't render it. An unknownstyle falls back to dark.
Server status check
The endpoint behind the status checker. It asks our pinger to reach a server from the outside and report what it finds.
POST /v1/tools/check
Content-Type: application/json
{ "game": "minecraft_java", "address": "play.example.com", "port": 25565, "token": "<turnstile>" }It needs a Cloudflare Turnstile token solved with action tools, and allows 15 checks per IP per day. Both exist so this can't be used to scan networks, which is also why private and link-local addresses are rejected outright. In practice that makes it a page to use, not an API to integrate — if you want programmatic checks for servers you own, tell us and we'll wire it to your server key instead.
Response: { available, verdict, players?, version?, description? } whereverdict is verified, unreachable,wrong_game or unsupported. available:false meansour checker didn't answer and says nothing about the server.