Skip to main content

Overview

The WebSocket API provides real-time bidirectional communication with Modrinth Hosting servers. It enables:
  • Real-time console log streaming
  • Server statistics monitoring (CPU, RAM, network)
  • Power state change notifications
  • Backup progress tracking
  • Mod installation status updates
  • Sending console commands
WebSocket connections are automatically authenticated using JWT tokens and support auto-reconnection with exponential backoff.

Connection Flow

The WebSocket client handles the complete authentication flow automatically:
1. client.archon.sockets.safeConnect(serverId)

2. Fetches JWT token via archon.servers_v0.getWebSocketAuth(serverId)

3. Opens WebSocket connection to wss://[url]

4. Sends authentication message: { event: 'auth', jwt: token }

5. Server responds with { event: 'auth-ok' }

6. Connection ready - start receiving events

Connecting to a Server

import { GenericModrinthClient } from '@modrinth/api-client'

const client = new GenericModrinthClient({
  token: 'your-auth-token'
})

// Connect to server WebSocket
await client.archon.sockets.safeConnect(serverId)

// Subscribe to events
const unsub = client.archon.sockets.on(serverId, 'log', (event) => {
  console.log(`[${event.stream}] ${event.message}`)
})

// Clean up when done
unsub()
client.archon.sockets.disconnect(serverId)

Safe Connect Options

// Connect only if not already connected
await client.archon.sockets.safeConnect(serverId)

// Force reconnect even if already connected
await client.archon.sockets.safeConnect(serverId, { force: true })

Event Types

Log Events

Real-time console output from the server.
client.archon.sockets.on(serverId, 'log', (event) => {
  console.log(`[${event.stream}] ${event.message}`)
})
event
string
Always "log"
stream
string
Output stream: stdout or stderr
message
string
Log message content

Stats Events

Server resource usage statistics, sent periodically.
client.archon.sockets.on(serverId, 'stats', (event) => {
  const cpuPercent = event.cpu_percent
  const ramUsedGB = event.ram_usage_bytes / (1024 ** 3)
  const ramTotalGB = event.ram_total_bytes / (1024 ** 3)
  
  console.log(`CPU: ${cpuPercent.toFixed(1)}%`)
  console.log(`RAM: ${ramUsedGB.toFixed(2)}GB / ${ramTotalGB.toFixed(2)}GB`)
})
event
string
Always "stats"
cpu_percent
number
CPU usage percentage (0-100)
ram_usage_bytes
number
Current RAM usage in bytes
ram_total_bytes
number
Total RAM available in bytes
storage_usage_bytes
number
Current storage usage in bytes
storage_total_bytes
number
Total storage available in bytes
net_tx_bytes
number
Network bytes transmitted
net_rx_bytes
number
Network bytes received

Power State Events

Notifications when server power state changes.
client.archon.sockets.on(serverId, 'power-state', (event) => {
  console.log(`Server is now: ${event.state}`)
  
  if (event.state === 'crashed') {
    if (event.oom_killed) {
      console.error('Server crashed due to out of memory')
    }
    console.error(`Exit code: ${event.exit_code}`)
  }
})
event
string
Always "power-state"
state
string
Power state: running, stopped, starting, stopping, or crashed
oom_killed
boolean
Present when state is crashed - indicates if killed due to out of memory
exit_code
number
Present when state is crashed - process exit code

Uptime Events

Server uptime information.
client.archon.sockets.on(serverId, 'uptime', (event) => {
  const hours = Math.floor(event.uptime / 3600)
  const minutes = Math.floor((event.uptime % 3600) / 60)
  console.log(`Server uptime: ${hours}h ${minutes}m`)
})
event
string
Always "uptime"
uptime
number
Server uptime in seconds

Backup Progress Events

Track backup creation, restoration, or file operations.
client.archon.sockets.on(serverId, 'backup-progress', (event) => {
  const percent = (event.progress * 100).toFixed(1)
  console.log(`${event.task}: ${percent}% (${event.state})`)
  
  if (event.state === 'done') {
    console.log(`Backup ${event.id} completed`)
  } else if (event.state === 'failed') {
    console.error(`Backup ${event.id} failed`)
  }
})
event
string
Always "backup-progress"
id
string
Backup ID (UUID)
task
string
Task type: file, create, or restore
state
string
Task state: ongoing, done, failed, cancelled, or unchanged
progress
number
Progress value from 0.0 to 1.0

Installation Result Events

Mod installation success or failure notifications.
client.archon.sockets.on(serverId, 'installation-result', (event) => {
  if (event.result === 'ok') {
    console.log('Mod installed successfully')
  } else {
    console.error(`Installation failed: ${event.reason}`)
  }
})
event
string
Always "installation-result"
result
string
Installation result: ok or err
reason
string
Error message (only present when result is err)

New Mod Events

Notification when a new mod is detected on the server.
client.archon.sockets.on(serverId, 'new-mod', (event) => {
  console.log(`New mod detected: ${event.project_id}`)
  console.log(`Version: ${event.version_id}`)
})
event
string
Always "new-mod"
project_id
string
Modrinth project ID
version_id
string
Modrinth version ID

Filesystem Operation Events

Track long-running filesystem operations (e.g., archive extraction).
client.archon.sockets.on(serverId, 'filesystem-ops', (event) => {
  event.all.forEach((op) => {
    const percent = (op.progress * 100).toFixed(1)
    console.log(`${op.op} (${op.id}): ${percent}%`)
    console.log(`Files: ${op.files_processed}, Bytes: ${op.bytes_processed}`)
    
    if (op.current_file) {
      console.log(`Current: ${op.current_file}`)
    }
    
    if (op.state === 'failure-invalid-path') {
      console.error(`Invalid path: ${op.invalid_path}`)
    }
  })
})
event
string
Always "filesystem-ops"
all
array
Array of filesystem operations
op
string
Operation type (currently only unarchive)
id
string
Operation ID (UUID)
progress
number
Progress from 0.0 to 1.0
bytes_processed
number
Number of bytes processed
files_processed
number
Number of files processed
state
string
State: queued, ongoing, done, cancelled, failure-corrupted, or failure-invalid-path
mime
string
MIME type of the archive
current_file
string
Currently processing file
invalid_path
string
Invalid path that caused failure
src
string
Source file path
started
string
ISO 8601 timestamp when operation started

Authentication Events

WebSocket authentication status notifications.
// Authentication successful
client.archon.sockets.on(serverId, 'auth-ok', (event) => {
  console.log('WebSocket authenticated')
})

// Token expiring soon (handled automatically)
client.archon.sockets.on(serverId, 'auth-expiring', (event) => {
  console.log('Auth token expiring, refreshing...')
})

// Authentication failed
client.archon.sockets.on(serverId, 'auth-incorrect', (event) => {
  console.error('WebSocket authentication failed')
})
event
string
auth-ok, auth-expiring, or auth-incorrect
The client automatically handles auth-expiring events by fetching a new token and re-authenticating.

Sending Commands

Send console commands to the server.
// Send a single command
client.archon.sockets.send(serverId, {
  event: 'command',
  cmd: '/say Hello from the API!'
})

// Stop the server via console
client.archon.sockets.send(serverId, {
  event: 'command',
  cmd: '/stop'
})

// Give a player operator status
client.archon.sockets.send(serverId, {
  event: 'command',
  cmd: '/op PlayerName'
})
serverId
string
required
The server ID to send the command to
event
string
required
Always "command"
cmd
string
required
The console command to execute (include leading / for game commands)

Auto-Reconnection

The WebSocket client automatically reconnects on unexpected disconnections using exponential backoff:
  • Base delay: 1 second
  • Max delay: 30 seconds
  • Max attempts: 10
  • Backoff strategy: Exponential with jitter
// Check connection status
const status = client.archon.sockets.getStatus(serverId)

if (status) {
  console.log('Connected:', status.connected)
  console.log('Reconnecting:', status.reconnecting)
  console.log('Reconnect attempts:', status.reconnectAttempts)
}

Manual Reconnection

// Disconnect and reconnect
client.archon.sockets.disconnect(serverId)
await client.archon.sockets.safeConnect(serverId)

// Force reconnect even if already connected
await client.archon.sockets.safeConnect(serverId, { force: true })

Complete Example: Server Console

Here’s a complete example building a server console interface:
import { GenericModrinthClient } from '@modrinth/api-client'

class ServerConsole {
  constructor(serverId, authToken) {
    this.serverId = serverId
    this.client = new GenericModrinthClient({ token: authToken })
    this.logs = []
    this.stats = null
    this.powerState = 'unknown'
  }

  async connect() {
    // Connect to WebSocket
    await this.client.archon.sockets.safeConnect(this.serverId)

    // Subscribe to logs
    this.unsubscribers = [
      this.client.archon.sockets.on(this.serverId, 'log', (event) => {
        this.logs.push({ stream: event.stream, message: event.message })
        this.onLog?.(event)
      }),

      // Subscribe to stats
      this.client.archon.sockets.on(this.serverId, 'stats', (event) => {
        this.stats = event
        this.onStats?.(event)
      }),

      // Subscribe to power state
      this.client.archon.sockets.on(this.serverId, 'power-state', (event) => {
        this.powerState = event.state
        this.onPowerState?.(event)
      }),

      // Subscribe to backup progress
      this.client.archon.sockets.on(this.serverId, 'backup-progress', (event) => {
        this.onBackupProgress?.(event)
      }),

      // Subscribe to installation results
      this.client.archon.sockets.on(this.serverId, 'installation-result', (event) => {
        this.onInstallationResult?.(event)
      })
    ]

    console.log('Connected to server console')
  }

  sendCommand(cmd) {
    this.client.archon.sockets.send(this.serverId, {
      event: 'command',
      cmd
    })
  }

  async startServer() {
    await this.client.archon.servers_v0.power(this.serverId, 'Start')
  }

  async stopServer() {
    await this.client.archon.servers_v0.power(this.serverId, 'Stop')
  }

  async restartServer() {
    await this.client.archon.servers_v0.power(this.serverId, 'Restart')
  }

  disconnect() {
    // Unsubscribe from all events
    this.unsubscribers?.forEach((unsub) => unsub())
    
    // Disconnect WebSocket
    this.client.archon.sockets.disconnect(this.serverId)
    
    console.log('Disconnected from server console')
  }
}

// Usage
const console = new ServerConsole('server-id', 'auth-token')

// Set up event handlers
console.onLog = (event) => {
  console.log(`[${event.stream}] ${event.message}`)
}

console.onStats = (event) => {
  const cpuPercent = event.cpu_percent.toFixed(1)
  const ramGB = (event.ram_usage_bytes / (1024 ** 3)).toFixed(2)
  console.log(`CPU: ${cpuPercent}% | RAM: ${ramGB}GB`)
}

console.onPowerState = (event) => {
  console.log(`Power state changed: ${event.state}`)
}

// Connect and use
await console.connect()

// Send commands
console.sendCommand('/list')
console.sendCommand('/say Server managed via API')

// Control power
await console.startServer()

// Clean up when done
process.on('SIGINT', () => {
  console.disconnect()
  process.exit()
})

Example: Real-time Server Dashboard

import { GenericModrinthClient } from '@modrinth/api-client'

const client = new GenericModrinthClient({ token: 'auth-token' })
const serverId = 'server-id'

// Connect to server
await client.archon.sockets.safeConnect(serverId)

// Build real-time dashboard
const dashboard = {
  powerState: 'unknown',
  cpuUsage: 0,
  ramUsage: 0,
  ramTotal: 0,
  uptime: 0,
  recentLogs: [],
  activeBackups: new Map()
}

// Update power state
client.archon.sockets.on(serverId, 'power-state', (event) => {
  dashboard.powerState = event.state
  updateUI()
})

// Update stats every few seconds
client.archon.sockets.on(serverId, 'stats', (event) => {
  dashboard.cpuUsage = event.cpu_percent
  dashboard.ramUsage = event.ram_usage_bytes
  dashboard.ramTotal = event.ram_total_bytes
  updateUI()
})

// Update uptime
client.archon.sockets.on(serverId, 'uptime', (event) => {
  dashboard.uptime = event.uptime
  updateUI()
})

// Track recent logs
client.archon.sockets.on(serverId, 'log', (event) => {
  dashboard.recentLogs.push(event)
  if (dashboard.recentLogs.length > 100) {
    dashboard.recentLogs.shift() // Keep only last 100 logs
  }
  updateUI()
})

// Track backup progress
client.archon.sockets.on(serverId, 'backup-progress', (event) => {
  dashboard.activeBackups.set(event.id, {
    task: event.task,
    progress: event.progress,
    state: event.state
  })
  
  if (event.state === 'done' || event.state === 'failed') {
    setTimeout(() => {
      dashboard.activeBackups.delete(event.id)
      updateUI()
    }, 3000) // Remove after 3 seconds
  }
  
  updateUI()
})

function updateUI() {
  console.clear()
  console.log('=== Server Dashboard ===')
  console.log(`Power State: ${dashboard.powerState}`)
  console.log(`CPU: ${dashboard.cpuUsage.toFixed(1)}%`)
  console.log(`RAM: ${(dashboard.ramUsage / 1024**3).toFixed(2)}GB / ${(dashboard.ramTotal / 1024**3).toFixed(2)}GB`)
  console.log(`Uptime: ${Math.floor(dashboard.uptime / 3600)}h ${Math.floor((dashboard.uptime % 3600) / 60)}m`)
  
  if (dashboard.activeBackups.size > 0) {
    console.log('\nActive Backups:')
    dashboard.activeBackups.forEach((backup, id) => {
      const percent = (backup.progress * 100).toFixed(1)
      console.log(`  ${backup.task}: ${percent}% (${backup.state})`)
    })
  }
  
  console.log('\nRecent Logs:')
  dashboard.recentLogs.slice(-10).forEach((log) => {
    console.log(`  [${log.stream}] ${log.message}`)
  })
}

Platform Support

WebSocket functionality is only available in the GenericModrinthClient, which uses the browser’s native WebSocket API. It is not available in:
  • NuxtModrinthClient (SSR context)
  • TauriModrinthClient (use Tauri’s WebSocket plugin instead)
import { GenericModrinthClient } from '@modrinth/api-client'

// WebSocket support available
const client = new GenericModrinthClient({ token: 'auth-token' })
client.archon.sockets // WebSocketClient instance