API
Live DonutSMP data as JSON.
Getting started
- 1Sign up
- 2Add Litecoin and buy the plan
- 3Create a key
curl https://api.donutapi.net/v1/balance/Steve \ -H "X-API-Key: donut_your_key"
Conventions
- Base URL
https://api.donutapi.net/v1- Auth
X-API-Keyheader orAuthorization: Bearer- Numbers
- Rounded like the game shows them.
displayhas the original text. - Times
- Unix seconds
- Errors
{ "error": "..." }with a 4xx or 5xx status- Rate limit
- Per key, per minute. See
X-RateLimit-Remaining. - Cache
- Repeat lookups are cached for a short time (
cached: true).
Errors
400- Not a valid username
401- Missing or wrong key
402- Your plan has run out
404- Player not found, or no watch set
429- Too many requests, wait a minute
503- Busy or too slow, try again
Players
/balance/{username}A player's money. Bedrock players work with or without the dot in front of their name.
curl https://api.donutapi.net/v1/balance/Steve \ -H "X-API-Key: donut_your_key"
{
"username": "Steve",
"balance": 1500000,
"display": "1.5M",
"fetchedAt": 1791234567,
"cached": false
}Stats
/stats/{username}A player's stats. Playtime is in seconds. display has each value as the game shows it.
curl https://api.donutapi.net/v1/stats/Steve \ -H "X-API-Key: donut_your_key"
{
"username": "Steve",
"stats": {
"money": 17800,
"shards": 121,
"kills": 610,
"deaths": 652,
"playtime": 3816000,
"blocksPlaced": 330000,
"blocksBroken": 343000
},
"display": {
"money": "17.8K",
"playtime": "44d 4h"
},
"fetchedAt": 1791234567,
"cached": false
}Payments
/watchWatch a player, usually your own shop account. One at a time; sending another name switches to it. webhookUrl is optional.
{ "username": "YourShop", "webhookUrl": "https://your-site.com/donut-payments" }/watchYour watch. status is starting (takes a few minutes), active, not_found (never joined DonutSMP) or paused (plan ran out).
{
"username": "YourShop",
"status": "active",
"watchingSince": 1791234000,
"webhookUrl": "https://your-site.com/donut-payments",
"webhookSecret": "whsec_...",
"createdAt": 1791233800
}/payments/confirm?from={username}&amount={n}&since={unix}Did this player pay you since this time? amount is optional and matched the way the game rounds (1.5K). If paid is false and certain is false, we were not watching the whole time, so check again later.
curl https://api.donutapi.net/v1/payments/confirm?from=Steve&amount=1500&since=1791234000 \ -H "X-API-Key: donut_your_key"
{
"paid": true,
"certain": true,
"payment": { "id": 42, "from": "Steve", "to": "YourShop", "amount": 1500, "display": "1.5K", "at": 1791234567 },
"status": "active",
"watchingSince": 1791234000
}/payments?from={username}&page={n}Payments to or from the player you watch, newest first, 10 per page. from is optional.
{
"items": [{ "id": 42, "from": "Steve", "to": "YourShop", "amount": 1500, "display": "1.5K", "at": 1791234567 }],
"page": 1, "pageSize": 10, "total": 1
}/paymentsClears the payments list. New payments keep coming in, and confirm still checks the cleared ones.
/watch/test-webhookSends a test payment to your webhook now and says whether your server answered OK.
{ "ok": true, "error": null }/watch/secretMakes a new webhook signing secret. The old one stops working at once.
/watchStop watching.
Account
/meWhen your plan ends and your rate limit.
{ "planUntil": 1793826567, "rateLimit": 60 }Webhooks
If your watch has a webhook URL, every payment is sent there as a POST. Check the signature before you trust it.
- Signature
X-Donutapi-Signature: HMAC-SHA256 of{X-Donutapi-Timestamp}.{body}with your signing secret- Secret
- On your Payment watch page, or in GET /watch
- Answer
- Reply with any 2xx status within 10 seconds
- Retries
- Up to 8 times over about two hours
- Test
- The Send a test button, or POST /watch/test-webhook, sends event test
{
"event": "payment",
"watching": "YourShop",
"id": 42,
"from": "Steve",
"to": "YourShop",
"amount": 1500,
"display": "1.5K",
"at": 1791234567
}import { createHmac, timingSafeEqual } from 'node:crypto';
export function verifyWebhook(rawBody, headers, secret) {
const expected = 'sha256=' + createHmac('sha256', secret)
.update(headers['x-donutapi-timestamp'] + '.' + rawBody)
.digest('hex');
const given = headers['x-donutapi-signature'] ?? '';
return given.length === expected.length &&
timingSafeEqual(Buffer.from(given), Buffer.from(expected));
}Examples
Keep your key on your server, never in a browser.
const res = await fetch('https://api.donutapi.net/v1/stats/Steve', {
headers: { 'X-API-Key': process.env.DONUTAPI_KEY },
});
if (!res.ok) throw new Error((await res.json()).error);
const { stats } = await res.json();