Overview

The PlayerLogger API provides programmatic access to player statistics and server data from Hytale servers running the PlayerLogger plugin. The API follows RESTful conventions and returns JSON responses.

Key Features

Real-time player stats, server aggregates, leaderboards, and Discord bot integration support.

Quick Start

Fetch stats for any registered server with a simple GET request:

curl https://api.hytaletravelers.com/?server=hytaletravelers.com

Authentication

The PlayerLogger API uses IP-based authentication for write operations. No API keys are required for read operations.

Operation Authentication Description
Read (GET) None Publicly accessible for fetching server stats
Write (POST) IP-based Server identified by its public IP address
Server Name Protection

Server aliases (names) are bound to the first IP that claims them. Attempting to use another server's name will return a 403 error.

Base URL

All API requests should be made to:

Base URL https://api.hytaletravelers.com

CORS Support

The API supports Cross-Origin Resource Sharing (CORS) for all origins, enabling browser-based applications:

Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type

Rate Limits

To ensure fair usage and server stability:

Use Case Recommended Interval
Real-time dashboard 5 seconds minimum
Discord bots 30 seconds minimum
General polling 30 seconds recommended

Get Server Stats

GET /?server={server_name}

Retrieve complete statistics for a specific server including all player data and aggregate stats.

Query Parameters

Parameter Type Required Description
server string Yes* Server name or IP address
ip string Yes* Alternative: Server IP address only

* One of server or ip is required

Example Request

GET https://api.hytaletravelers.com/?server=hytaletravelers.com

Response 200 OK

{
  "stats": {
    "totalPlayers": 42,
    "onlinePlayers": 5,
    "totalPlaytimeSeconds": 360000,
    "totalDamageDealt": 15420.5,
    "totalPlayerKills": 128,
    "totalMobKills": 4521,
    "totalDeaths": 89,
    "totalPvpDeaths": 34,
    "totalPveDeaths": 55,
    "totalBlocksPlaced": 15420,
    "totalBlocksBroken": 22150
  },
  "players": [
    {
      "uuid": "550e8400-e29b-41d4-a716-446655440000",
      "username": "PlayerName",
      "playtimeSeconds": 7200,
      "playtimeFormatted": "2h 0m 0s",
      "online": true,
      "firstJoined": 1704067200000,
      "firstJoinedFormatted": "Jan 1, 2024",
      "lastSeen": 1706500000000,
      "lastSeenFormatted": "Jan 29, 2024",
      "damageDealt": 1250.5,
      "playerKills": 3,
      "mobKills": 127,
      "deathCount": 5,
      "pvpDeaths": 2,
      "pveDeaths": 3,
      "lastDeathCause": "Killed by Zombie",
      "blocksPlaced": 500,
      "blocksBroken": 832
    }
  ],
  "serverIP": "192.168.1.100",
  "serverName": "hytaletravelers.com",
  "publicListing": true,
  "lastUpdated": 1706500000000
}

List All Servers

GET /servers

Retrieve a list of all registered servers. Optionally filter to only show publicly listed servers.

Query Parameters

Parameter Type Required Description
public string No Set to "true" to only return publicly listed servers

Example Request

GET https://api.hytaletravelers.com/servers?public=true

Response 200 OK

{
  "servers": [
    "hytaletravelers.com",
    "play.anotherserver.net",
    "192.168.1.50"
  ]
}

Upload Stats

POST /

Upload player statistics from a game server. Used by the PlayerLogger plugin to sync data.

Plugin Use Only

This endpoint is designed for the PlayerLogger plugin. Server identity is determined by the request's source IP.

Request Body

{
  "serverName": "connect.myserver.com",
  "publicListing": true,
  "lastUpdated": 1706500000000,
  "players": [
    {
      "uuid": "550e8400-e29b-41d4-a716-446655440000",
      "username": "PlayerName",
      "playtimeSeconds": 7200,
      "playtimeFormatted": "2h 0m 0s",
      "online": true,
      "firstJoined": 1704067200000,
      "firstJoinedFormatted": "Jan 1, 2024",
      "lastSeen": 1706500000000,
      "lastSeenFormatted": "Jan 29, 2024",
      "damageDealt": 1250.5,
      "playerKills": 3,
      "mobKills": 127,
      "deathCount": 5,
      "pvpDeaths": 2,
      "pveDeaths": 3,
      "lastDeathCause": "Killed by Zombie",
      "blocksPlaced": 500,
      "blocksBroken": 832
    }
  ],
  "stats": {
    "totalPlayers": 42,
    "onlinePlayers": 5,
    "totalPlaytimeSeconds": 360000,
    "totalDamageDealt": 15420.5,
    "totalPlayerKills": 128,
    "totalMobKills": 4521,
    "totalDeaths": 89,
    "totalPvpDeaths": 34,
    "totalPveDeaths": 55,
    "totalBlocksPlaced": 15420,
    "totalBlocksBroken": 22150
  }
}

Response 200 OK

{
  "success": true,
  "message": "Data stored for connect.myserver.com"
}

Fetch Own Data

GET /fetch

Allows the plugin to retrieve its own stored data on startup for anti-tampering verification.

Response 200 OK

{
  "players": [...],
  "stats": {...},
  "lastUpdated": 1706500000000,
  "serverName": "connect.myserver.com",
  "serverIP": "192.168.1.100"
}

Version Check

GET /plugins/playerlogger/version

Returns the latest plugin version from CurseForge. Used by the plugin to check for updates.

Response 200 OK

{
  "version": "1.0.0"
}

Local Plugin API

The PlayerLogger plugin includes a built-in webserver that exposes local endpoints. Enable this in your plugin config with webEnabled: true and configure the port with webPort.

Self-Hosted Setup

These endpoints allow you to build custom dashboards or integrations that run directly on your server without using the public API.

Base URL http://localhost:{webPort}

GET /api/players

GET /api/players

Returns a list of all players sorted by playtime (descending). Filters out invalid "ghost" entries.

Response 200 OK

[
  {
    "uuid": "550e8400-e29b-41d4-a716-446655440000",
    "username": "PlayerName",
    "playtimeSeconds": 7200,
    "playtimeFormatted": "2h 0m 0s",
    "online": true,
    "firstJoined": 1704067200000,
    "firstJoinedFormatted": "Jan 1, 2024",
    "lastSeen": 1706500000000,
    "lastSeenFormatted": "Jan 29, 2024",
    "damageDealt": 1250.5,
    "playerKills": 3,
    "mobKills": 127,
    "deathCount": 5,
    "pvpDeaths": 2,
    "pveDeaths": 3,
    "lastDeathCause": "Killed by Zombie",
    "blocksPlaced": 500,
    "blocksBroken": 832
  }
]

GET /api/stats

GET /api/stats

Returns aggregated server-wide statistics.

Response 200 OK

{
  "totalPlayers": 42,
  "onlinePlayers": 5,
  "totalPlaytimeSeconds": 360000,
  "totalDamageDealt": 15420.5,
  "totalPlayerKills": 128,
  "totalMobKills": 4521,
  "totalDeaths": 89,
  "totalPvpDeaths": 34,
  "totalPveDeaths": 55,
  "totalBlocksPlaced": 15420,
  "totalBlocksBroken": 22150
}

GET /api/fetch

GET /api/fetch

Fetch endpoint for API-first mode. Returns all player data with a timestamp, used to load data from remote API on startup.

Response 200 OK

{
  "players": [...],
  "lastUpdated": 1706500000000
}

POST /api/push

POST /api/push

Accept pushed data from other servers. Useful for self-hosted setups where you want to aggregate data from multiple servers.

Request Body

Same format as the public API Upload Stats endpoint.

Response 200 OK

{
  "success": true,
  "message": "Data received",
  "timestamp": 1706500000000
}

Player Object

The player object contains all tracked statistics for an individual player.

Identity Fields

Field Type Description
uuid string Player's unique identifier (UUID)
username string Player's display name
online boolean Whether player is currently online

Playtime & Activity

Field Type Description
playtimeSeconds integer Total time played in seconds
playtimeFormatted string Human-readable playtime (e.g., "2h 30m 15s")
firstJoined integer Epoch timestamp (ms) of first join to server
firstJoinedFormatted string Human-readable first join date
lastSeen integer Epoch timestamp (ms) of last activity
lastSeenFormatted string Human-readable last seen date

Combat Statistics

Field Type Description
damageDealt float Total damage dealt by player
playerKills integer Number of PvP kills
mobKills integer Number of mob/PvE kills
deathCount integer Total number of deaths
pvpDeaths integer Deaths caused by other players
pveDeaths integer Deaths caused by mobs or environment
lastDeathCause string|null Description of last death (e.g., "Killed by Zombie", "Fell to their death")

Block Interaction

Field Type Description
blocksPlaced integer Total blocks placed
blocksBroken integer Total blocks broken/mined

Stats Object

Aggregate statistics for the entire server.

Field Type Description
totalPlayers integer Total unique players tracked
onlinePlayers integer Currently online player count
totalPlaytimeSeconds integer Combined playtime of all players
totalDamageDealt float Combined damage dealt
totalPlayerKills integer Server-wide PvP kills
totalMobKills integer Server-wide mob kills
totalDeaths integer Server-wide death count (all deaths)
totalPvpDeaths integer Server-wide PvP deaths
totalPveDeaths integer Server-wide PvE/environmental deaths
totalBlocksPlaced integer Total blocks placed on server
totalBlocksBroken integer Total blocks broken on server

Error Responses

The API returns standard HTTP status codes along with JSON error details.

400 Bad Request

{
  "error": "Missing server parameter",
  "usage": "GET /?server=connect.myserver.com or GET /?ip=x.x.x.x"
}

403 Forbidden

{
  "error": "Server name already claimed",
  "message": "The name 'servername' is registered to another server."
}

404 Not Found

{
  "error": "Server not found",
  "message": "No data for: unknownserver.com"
}

405 Method Not Allowed

{
  "error": "Method not allowed"
}

JavaScript Example

Fetch server stats and display a player leaderboard:

// Fetch server statistics
async function getServerStats(serverName) {
  const response = await fetch(
    `https://api.hytaletravelers.com/?server=${encodeURIComponent(serverName)}`
  );

  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }

  return await response.json();
}

// Get top players by playtime
function getTopPlayers(players, limit = 10) {
  return players
    .sort((a, b) => b.playtimeSeconds - a.playtimeSeconds)
    .slice(0, limit);
}

// Usage example
getServerStats('hytaletravelers.com')
  .then(data => {
    console.log(`Server: ${data.serverName}`);
    console.log(`Online: ${data.stats.onlinePlayers}/${data.stats.totalPlayers}`);

    const topPlayers = getTopPlayers(data.players, 5);
    topPlayers.forEach((player, i) => {
      console.log(`${i + 1}. ${player.username} - ${player.playtimeFormatted}`);
    });
  })
  .catch(err => console.error('Error:', err));

Python Example

Simple Python script to fetch and display server stats:

import requests

def get_server_stats(server_name):
    """Fetch statistics for a specific server."""
    url = f"https://api.hytaletravelers.com/?server={server_name}"
    response = requests.get(url)
    response.raise_for_status()
    return response.json()

def get_online_players(data):
    """Filter to only online players."""
    return [p for p in data['players'] if p['online']]

# Usage
if __name__ == "__main__":
    data = get_server_stats("hytaletravelers.com")

    print(f"Server: {data['serverName']}")
    print(f"Total Players: {data['stats']['totalPlayers']}")
    print(f"Online Now: {data['stats']['onlinePlayers']}")

    print("\nOnline Players:")
    for player in get_online_players(data):
        print(f"  - {player['username']} ({player['playtimeFormatted']})")

cURL Examples

Get Server Stats

curl -X GET "https://api.hytaletravelers.com/?server=hytaletravelers.com"

List All Public Servers

curl -X GET "https://api.hytaletravelers.com/servers?public=true"

Pretty Print with jq

curl -s "https://api.hytaletravelers.com/?server=hytaletravelers.com" | jq '.'

Get Only Online Players

curl -s "https://api.hytaletravelers.com/?server=hytaletravelers.com" | jq '.players | map(select(.online == true))'

Interactive Demo

See the API in action with this live splitscreen demo. The left panel shows the implementation code, and the right panel shows the working result.

index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Server Stats</title>
  <style>
    body {
      font-family: system-ui, sans-serif;
      background: #1a1a2e;
      color: #eee;
      padding: 20px;
      margin: 0;
    }
    .card {
      background: #16213e;
      border-radius: 12px;
      padding: 20px;
      margin-bottom: 16px;
    }
    h1 { color: #58a6ff; margin-top: 0; }
    .stat-grid {
      display: grid;
      grid-template-columns: repeat(3, 1fr);
      gap: 12px;
    }
    .stat {
      background: #0f3460;
      padding: 12px;
      border-radius: 8px;
      text-align: center;
    }
    .stat-value {
      font-size: 24px;
      font-weight: bold;
      color: #58a6ff;
    }
    .stat-label {
      font-size: 12px;
      color: #8b949e;
    }
    .player {
      display: flex;
      align-items: center;
      padding: 8px;
      border-bottom: 1px solid #30363d;
    }
    .player img {
      width: 32px;
      height: 32px;
      border-radius: 4px;
      margin-right: 12px;
    }
    .online { color: #3fb950; }
    .offline { color: #8b949e; }
    #error { color: #f85149; }
  </style>
</head>
<body>
  <div class="card">
    <h1 id="serverName">Loading...</h1>
    <div class="stat-grid">
      <div class="stat">
        <div class="stat-value" id="online">-</div>
        <div class="stat-label">Online</div>
      </div>
      <div class="stat">
        <div class="stat-value" id="total">-</div>
        <div class="stat-label">Total Players</div>
      </div>
      <div class="stat">
        <div class="stat-value" id="kills">-</div>
        <div class="stat-label">Mob Kills</div>
      </div>
    </div>
  </div>

  <div class="card">
    <h2>Top Players</h2>
    <div id="players"></div>
  </div>

  <div id="error"></div>

  <script>
    const SERVER = 'HytaleTravelers.com';

    async function fetchStats() {
      try {
        const res = await fetch(
          `https://api.hytaletravelers.com/?server=${SERVER}`
        );
        const data = await res.json();

        if (data.error) {
          document.getElementById('error').textContent = data.error;
          return;
        }

        // Update server info
        document.getElementById('serverName').textContent =
          data.serverName || SERVER;
        document.getElementById('online').textContent =
          data.stats.onlinePlayers;
        document.getElementById('total').textContent =
          data.stats.totalPlayers;
        document.getElementById('kills').textContent =
          data.stats.totalMobKills.toLocaleString();

        // Show top 5 players
        const top5 = data.players
          .sort((a, b) => b.playtimeSeconds - a.playtimeSeconds)
          .slice(0, 5);

        document.getElementById('players').innerHTML = top5
          .map(p => `
            <div class="player">
              <div>
                <strong>${p.username}</strong>
                <span class="${p.online ? 'online' : 'offline'}">
                  ${p.online ? ' ● Online' : ''}
                </span>
                <div style="font-size:12px;color:#8b949e">
                  ${p.playtimeFormatted}
                </div>
              </div>
            </div>
          `).join('');

      } catch (err) {
        document.getElementById('error').textContent =
          'Failed to load: ' + err.message;
      }
    }

    fetchStats();
    setInterval(fetchStats, 30000); // Refresh every 30s
  </script>
</body>
</html>
Live Preview