Developer API

Read-only JSON for everything DuckDuckRob shows publicly. No key, no signup, no writes. Base URL https://ogn.tropixmc.dev/api/v1.

Quick start

Every endpoint is a plain GET that returns JSON. Try one now:

curl https://ogn.tropixmc.dev/api/v1/leaderboards/messages?limit=3
{
  "key": "messages",
  "label": "Messages",
  "page": 1,
  "limit": 3,
  "total": 3612,
  "pages": 1204,
  "rows": [
    {
      "rank": 1,
      "username": "DirectCube",
      "value": 48213,
      "display": "48,213",
      "profileUrl": "https://example.com/profile/DirectCube",
      "apiUrl": "https://example.com/api/v1/players/DirectCube",
      "avatarUrl": "https://example.com/head/DirectCube?s=64"
    }
  ]
}

Start at the index for a machine-readable list of every endpoint, or pull the OpenAPI 3.1 document into a client generator.

Conventions

Versioning
The version lives in the path (/api/v1). Fields get added inside a version; nothing already documented is removed or renamed. Treat unknown fields as normal and ignore them.
Paging
List endpoints take ?page= (1-based, default 1) and ?limit= (default 20, max 100). Every list response carries page, limit, total and pages alongside the rows.
Timestamps
Milliseconds since the Unix epoch, as numbers. null means never recorded. Bucketed chart points use { "t": <ms>, "v": <value> }.
Money and figures
Numbers are raw, unformatted values. Where the site also prints a formatted string it ships next to the number (value and display, moneyValue and money). A figure the server has never reported for that player is null, not -1 or 0.
Rate limit
120 requests per 60 seconds per IP. Responses carry X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (epoch seconds); going over returns 429 with Retry-After.
Caching
Successful responses set Cache-Control: public, max-age=30. The live endpoints (/api/v1/health, /api/v1/online) send no-store.
CORS
Access-Control-Allow-Origin: *, so browser code can call it directly. The API never reads your session cookie, so it returns the same body to everyone, logged in or not.
Maintenance
While the site is locked for maintenance every endpoint answers 503 with the error body below. Retry later.

Errors

Failures use the HTTP status plus one consistent body:

{ "error": { "code": "not_found", "message": "No tracked player by that name." } }
StatusCodeMeaning
400missing_queryA required query parameter was absent.
400unknown_valueThe trait value did not match any option. The message lists the valid ones.
403section_hiddenThe player switched that section off on their account page.
403login_onlyThe player limits their profile to logged-in viewers, so it is never served over the API.
404not_foundNo such player, guild, warp or endpoint.
404opted_outThe player opted out of the profiler entirely.
404unknown_leaderboard, unknown_traitUnrecognised key. The listing endpoint has the valid ones.
405method_not_allowedThe API is read-only. Use GET.
429rate_limitedOver the rate limit. Wait for Retry-After.
503maintenanceThe site is locked for maintenance.

What the API will not show you

The API is held to the same rules as the pages. It never returns more than an anonymous visitor can already read on the site.

Meta

GET /api/v1 Try it

Index of every endpoint, plus the rate limit in force.

GET /api/v1/openapi.json Try it

OpenAPI 3.1 description of this API, for code generators and clients.

GET /api/v1/health Try it

Liveness check with the current row counts.

Server

GET /api/v1/stats Try it

Site-wide totals: players, messages, donations, quotes, guilds, warps, crashes.

GET /api/v1/online Try it

How many players are on the RPG server right now. Count only, never names.

Players

GET /api/v1/players Try it

The player directory, busiest first.

ParameterInTypeDescription
page query integer 1-based page number. Default 1.
limit query integer Rows per page. Default 20, max 100.
GET /api/v1/players/{name} Try it

One player: identity, activity, server figures, guild, donations, badges, joke stats.

ParameterInTypeDescription
name required path string Minecraft username, or any nickname the player has been seen under.
GET /api/v1/players/{name}/activity Try it

Hour-of-day histogram, a message-count series, and the money/petals history.

ParameterInTypeDescription
name required path string Minecraft username, or any nickname the player has been seen under.
range query string Window for the message series and the history: 1d, 7d, 30d or all. Default all.
GET /api/v1/players/{name}/words Try it

Vocabulary metrics with top words, bigrams and emojis. Off unless the player opted in.

ParameterInTypeDescription
name required path string Minecraft username, or any nickname the player has been seen under.
GET /api/v1/players/{name}/messages Try it

A deterministic sample of the player's stored messages. Off unless the player opted in.

ParameterInTypeDescription
name required path string Minecraft username, or any nickname the player has been seen under.
GET /api/v1/players/{name}/friends Try it

Friend edges: who this player added, who added them, plus the computed best friend.

ParameterInTypeDescription
name required path string Minecraft username, or any nickname the player has been seen under.

Leaderboards

GET /api/v1/leaderboards Try it

Every public leaderboard and its key.

GET /api/v1/leaderboards/{stat} Try it

One leaderboard, ranked. Accepts the aliases the site and the Discord bot accept.

ParameterInTypeDescription
stat required path string Board key from /leaderboards, e.g. messages.
page query integer 1-based page number. Default 1.
limit query integer Rows per page. Default 20, max 100.

Community

GET /api/v1/guilds Try it

Tracked guilds, strongest first.

ParameterInTypeDescription
q query string Filter by name substring.
page query integer 1-based page number. Default 1.
limit query integer Rows per page. Default 20, max 100.
GET /api/v1/guilds/{name} Try it

One guild with its roster, allies and enemies.

ParameterInTypeDescription
name required path string Guild name, case-insensitive.
GET /api/v1/warps Try it

Player warps, most visited first.

ParameterInTypeDescription
q query string Filter by warp name or owner substring.
page query integer 1-based page number. Default 1.
limit query integer Rows per page. Default 20, max 100.
GET /api/v1/warps/{name} Try it

One player warp in full.

ParameterInTypeDescription
name required path string Warp name, case-insensitive.
GET /api/v1/donations Try it

Donation totals with the ranked donor board and the recent feed.

ParameterInTypeDescription
page query integer 1-based page number. Default 1.
limit query integer Rows per page. Default 20, max 100.
GET /api/v1/quotes Try it

The quotebook, newest first.

ParameterInTypeDescription
page query integer 1-based page number. Default 1.
limit query integer Rows per page. Default 20, max 100.
GET /api/v1/wealth Try it

Balance distribution: median, mean, Gini, top shares, magnitude histogram, richest players.

Response samples

GET /api/v1/players/{name}

Trimmed. hidden names the sections the player switched off; every key it lists is null in the body.

{
  "username": "DirectCube",
  "nicks": ["Cube"],
  "resolved": true,
  "staff": false,
  "bio": null,
  "timezone": "UTC",
  "firstSeen": 1719792000000,
  "lastSeen": 1753315200000,
  "profileUrl": "https://example.com/profile/DirectCube",
  "roles": { "admin": true, "verifiedOwner": true, "plus": true, "donorTier": "gold", "coolKid": false },
  "donations": { "total": 250000000, "rank": 2, "tier": "gold" },
  "hidden": ["summary", "samples"],
  "activity": {
    "messagesStored": 12043, "messagesLifetime": 48213, "welcomes": 311,
    "avgLength": 27.4, "medianLength": 21, "avgWords": 5.2, "uniqueWords": 9218,
    "questionPercent": 12, "links": 84, "busiestHour": 21, "busiestDay": "Saturday"
  },
  "server": {
    "money": "$2,058,235,855.68", "moneyValue": 2058235855.68, "petals": 4120,
    "class": "Mage", "playtimeHours": 913, "chatgameMisses": 7,
    "claimBlocks": { "total": 182400, "fromPlaytime": 120000, "bonus": 62400, "spent": 40000, "left": 142400, "claims": 6 },
    "warpsOwned": 2, "topOnePercent": true, "polledAt": 1753315000000
  },
  "guild": { "name": "Yuri", "level": 14, "members": 22, "membersMax": 30, "role": "owner", "rank": 1 },
  "miscStats": [{ "label": "Star Sign", "value": "Leo" }],
  "casino": { "balance": 15200.5, "bets": 431, "wagered": 98000, "profit": -4200.25 },
  "badges": [{ "id": "admin", "label": "Admin", "description": "Site administrator" }]
}

GET /api/v1/players/{name}/activity

hours.counts is 24 entries, index 0 to 23 in the site timezone. messages.points fills quiet buckets with 0 so the series is never misdated.

{
  "username": "DirectCube",
  "range": "30d",
  "timezone": "UTC",
  "hours": { "counts": [12, 4, 1, 0, 0, 0, 2, 9, 40, 55, 61, 72, 80, 77, 69, 58, 63, 71, 88, 94, 101, 96, 60, 31], "total": 1144 },
  "messages": { "unit": "day", "points": [{ "t": 1750723200000, "v": 41 }, { "t": 1750809600000, "v": 0 }] },
  "history": { "money": [{ "t": 1750723200000, "v": 1900000000 }], "petals": [] },
  "hidden": []
}

GET /api/v1/guilds/{name}

{
  "name": "Yuri",
  "description": "no rules",
  "level": 14,
  "members": 22,
  "membersMax": 30,
  "owner": "DirectCube",
  "allies": ["Nova"],
  "enemies": [],
  "roster": [{ "username": "DirectCube", "role": "owner", "rank": 1 }],
  "polledAt": 1753315000000
}

Using it well

Questions, a raised limit, or a bug in a response: the community Discord. See also the Terms of Service and Privacy Policy.