Plugin API Reference
These are the endpoints the Paper plugin uses. They are documented for anyone building an alternative client, a bot, or a second server integration. Everything else in the API is either supporter-session or admin-session scoped and is not covered here.
Authentication
Every endpoint on this page except the health check requires the shared plugin API key, the same value as the plugin's api-key and the server's PLUGIN_API_KEY, sent as a bearer token. Requests without it are rejected.
The plugin also stamps each request with an x-traverse-plugin-version header. The server records it for the admin dashboard and the health check, and uses it to decide whether the plugin is version-compatible (same major.minor). It is informational, not an auth requirement.
The web app runs exactly one world. These endpoints always act on that world, so a worldId is no longer needed; older plugin builds that still send one are accepted and the value is ignored.
Anyone holding it can mint a link that claims land as any supporter. Treat it like a database password: keep it out of version control, do not expose the API to the open internet without need, and rotate it on both sides together.
Endpoints
GET /api/plugin/world
Returns the installation's world, its derived state, and the server version the plugin checks itself against.
{
"world": {"id": "...", "name": "Season 5", "state": "ACTIVE", "startAt": "...", "endAt": "..."},
"serverVersion": "1.1.0"
}
state is one of SCHEDULED, ACTIVE, ENDED, DISABLED. The world always exists, so there is no not-found case; a world that is off simply reports DISABLED.
POST /api/plugin/access
Creates the player and entitlement if the player qualifies, and returns a fresh 24-hour access code.
{
"minecraftUuid": "...",
"username": "Steve",
"permissions": ["traverse.supporter"],
"groups": ["supporter"],
"isOp": false
}
| Response | Meaning |
|---|---|
200 {"code": "..."} | Qualified. Any previously active code for this entitlement is revoked. |
403 NOT_ELIGIBLE | Does not match the world's qualification rule, and is not an operator. |
409 WORLD_NOT_ACTIVE | The world is scheduled, ended, or disabled. |
400 INVALID_REQUEST | The body failed validation. |
isOp: true bypasses the qualification check.
POST /api/plugin/access-token
Issues a single-use, 15-minute access token and the link to hand the player.
{"entitlementId": "..."}
{"token": "...", "url": "https://.../access/<token>", "expiresAt": "..."}
The URL is built from the server's PUBLIC_BASE_URL, which is why it must match the plugin's base-url.
GET /api/plugin/player/:uuid/status
Reports whether the player has an active claim, their entitlement id if they have one, and, when the campaign is inside its closing window and the player still has access without a claim, a closing reminder to show them.
{"hasSelection": true, "centerX": 1234, "centerZ": -5678, "radius": 75, "entitlementId": "..."}
With no claim but an entitlement inside the reminder window:
{"hasSelection": false, "entitlementId": "...", "closingReminder": {"closesAt": "..."}}
An unknown player returns {"hasSelection": false} rather than a 404.
POST /api/plugin/player/:uuid/closing-reminder
Marks the closing reminder as sent for the player's entitlement, so it is only ever shown once. The plugin calls this right after displaying the reminder. Returns 204, or 404 PLAYER_NOT_FOUND for an unknown player.
POST /api/plugin/entitlement/:entitlementId/access-code
Issues a fresh 24-hour access code for an entitlement that already exists, revoking any earlier active one.
GET /api/plugin/onboarding/:uuid?worldId=...
Asks whether the join dialog should be shown. Returns {"shouldShow": false} when the player has no entitlement for that world, has already been shown the dialog, or the world has no dialogTitle and dialogMessage configured. Otherwise it returns the dialog copy and a ready-to-use access URL.
POST /api/plugin/onboarding/:uuid/dismiss
Marks the dialog as shown so it never appears again for that entitlement. Body is {"worldId": "..."}. Returns 204.
Health check
GET /api/health
Unauthenticated, for Coolify or any uptime monitor. It returns 200 whenever the server and its database are reachable, and 503 only when the database probe fails.
{
"status": "ok",
"serverVersion": "1.1.0",
"database": "ok",
"world": { "state": "ACTIVE" },
"biomeIndex": { "state": "READY", "builtAt": "2026-10-10T09:00:00.000Z" },
"plugin": { "lastSeenAt": "2026-10-10T10:12:00.000Z", "version": "1.1.0", "compatible": true }
}
A disabled world, a missing biome index, or a quiet plugin are reported in the body (as null or a differing state), not as a failure, since none of them mean the container is broken. The failure body is {"status": "error", "database": "unreachable", "serverVersion": "..."}. No seed, coordinates, or player data are ever exposed.
Supporter-facing endpoint
GET /access/:token
Not part of the plugin API and not authenticated by the key: this is the link a player clicks. It redeems the token, sets the supporter session cookie, and redirects into the app. A failure redirects with ?accessError=INVALID|EXPIRED|USED, which the app surfaces once and then strips from the URL.
The token segment is redacted from request logs, so an unused token cannot be read out of a log or proxy trail and replayed.