DonutAPI

A simple REST API for DonutSMP player stats and online status.

Behind the API, a pool of Minecraft accounts stays logged in on DonutSMP. Each request is handed to a free account which runs the matching in-game command, reads the result, and returns it as JSON — so you get live, real data straight from the server.

Base URL

https://donutapi.site

Two endpoints, both GET and both returning JSON:

Authentication

Every request needs an API key, created on the dashboard. Send it in the Authorization header:

# any of these work
curl -H "Authorization: dnt_your_key" BASEURL/v1/stats/Notch
curl -H "Authorization: Bearer dnt_your_key" BASEURL/v1/stats/Notch
curl -H "X-API-Key: dnt_your_key" BASEURL/v1/stats/Notch

A missing or invalid key returns 401 unauthorized. Each key can have an optional per-minute rate limit.

Usernames — Java & Bedrock

Use the plain username for a Java player. For a Bedrock player, prefix it with a dot (.) — that's how DonutSMP stores Geyser players.

PlayerRequest
Java/v1/stats/Notch
Bedrock/v1/stats/.SomeGamertag

The response echoes which one it detected in the platform field ("java" or "bedrock").

Player stats

GET/v1/stats/:username

Runs /stats <username> in-game, parses the stats menu, and returns the numbers.

Example request

curl -H "Authorization: dnt_your_key" BASEURL/v1/stats/Kettunen47

Example response 200 OK

{
  "status": 200,
  "result": {
    "username": "Kettunen47",
    "platform": "java",
    "money": 6000000000,
    "shards": 2900,
    "kills": 17,
    "deaths": 47,
    "playtime": 2592000,
    "placed_blocks": 142000,
    "broken_blocks": 98000,
    "mobs_killed": 5400,
    "display": {
      "money": "6B", "shards": "2900", "playtime": "30d"
    }
  },
  "cached": false,
  "worker": "worker12",
  "took_ms": 359
}

Result fields

FieldTypeNotes
usernamestringas requested
platformstring"java" or "bedrock"
moneynumberexact balance, e.g. 6000000000
shardsnumber
kills / deathsnumber
playtimenumberseconds (e.g. 2592000 = 30 days)
placed_blocksnumber
broken_blocksnumber
mobs_killednumber
displayobjectthe same values exactly as shown in-game ("6B", "30d")
Numbers are always integers (seconds for playtime). The display object keeps the short in-game strings if you'd rather show those. Add ?raw=1 to also get fields — every item in the menu — for debugging.

Player activity (online check)

GET/v1/activity/:username

Whispers the player in-game and reads the server's reply to decide whether they're online. Returns fast (~200 ms).

Example request

curl -H "Authorization: dnt_your_key" BASEURL/v1/activity/Kettunen47

Online 200 OK

{
  "status": 200,
  "result": {
    "username": "Kettunen47",
    "platform": "java",
    "online": true,
    "status": "online",
    "reason": "message delivered"
  },
  "cached": false,
  "worker": "worker7",
  "took_ms": 206
}

A player who is online but only accepts messages from friends, or has PMs disabled, still returns online: true — the server only gives those replies for players who are actually online.

Offline 200 OK

{
  "status": 200,
  "result": {
    "username": "SomePlayer",
    "platform": "java",
    "online": false,
    "status": "offline",
    "reason": "not online"
  },
  "cached": false,
  "worker": "worker3",
  "took_ms": 190
}

Result fields

FieldTypeNotes
onlinebooleanthe quick answer — true / false
statusstring"online", "offline", or "not_found" (never joined)
reasonstringa short human-readable reason
detailstringthe exact server line, if you add ?raw=1
A player who has never joined the server returns online: false with status: "not_found". The server can't always tell "offline" from "never joined", so treat online as the reliable field.

Query options

ParamEffect
?fresh=1skip the cache and force a live lookup
?raw=1include extra detail — fields on stats, detail/chat on activity
curl -H "Authorization: dnt_your_key" "BASEURL/v1/stats/Notch?fresh=1&raw=1"

Caching

Identical lookups are served from a short in-memory cache, and identical requests already in flight are de-duplicated onto one in-game command. The cached field tells you whether a response came from cache. Use ?fresh=1 to bypass it. Typical windows: stats a few seconds, activity even shorter (it changes fast).

Rate limits

Each API key can have an optional requests-per-minute limit (0 = unlimited). When exceeded you get 429 with a Retry-After header:

{ "status": 429, "error": "rate_limited", "message": "rate limit of 120/min exceeded" }

Errors

Errors use the shape { status, error, message }. The error code is stable; the message is for humans.

HTTPerrorMeaning
400bad_requestinvalid username (not a Java name or .Bedrock name)
401unauthorizedmissing / invalid / disabled API key
404not_foundno such player (stats only)
429rate_limitedper-key limit exceeded — see Retry-After
502unparsedthe menu opened but couldn't be read (rare)
503no_workers / queue_timeoutno accounts online, or all busy too long
504timeoutthe in-game command didn't answer in time
{ "status": 404, "error": "not_found", "message": "User does not exist" }

Manage workers, API keys and proxies on the dashboard. Health check: GET /health.