Skip to main content
The @modrinth/api-client is a flexible, type-safe API client for Modrinth’s APIs (Labrinth, Kyros, and Archon). It works across Node.js, browsers, Nuxt, and Tauri with a feature system for authentication, retries, circuit breaking, and other custom request/response processing.

Installation

npm install @modrinth/api-client

Platform Support

The client provides three platform-specific implementations:
  • GenericModrinthClient - Uses ofetch, works in Node.js, browsers, and workers
  • NuxtModrinthClient - Uses Nuxt’s $fetch, SSR-aware, blocks uploads during SSR
  • TauriModrinthClient - Uses @tauri-apps/plugin-http for Tauri desktop apps

Creating a Client Instance

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

const client = new GenericModrinthClient({
  userAgent: 'my-app/1.0.0',
  features: [
    new AuthFeature({ token: 'mrp_...' })
  ]
})

Module Structure

Modules are lazy-loaded and accessed as a nested structure organized by service:
// Labrinth (main API)
client.labrinth.projects_v2
client.labrinth.projects_v3
client.labrinth.versions_v3
client.labrinth.collections
client.labrinth.threads_v3

// Archon (Modrinth Hosting)
client.archon.servers_v0
client.archon.servers_v1
client.archon.backups_v0
client.archon.backups_v1
client.archon.content_v0

// Kyros (file management)
client.kyros.files_v0

// ISO 3166 (country data)
client.iso3166.data

Authentication

The AuthFeature automatically injects authentication tokens into request headers. It supports both static and dynamic tokens.
import { AuthFeature } from '@modrinth/api-client'

new AuthFeature({
  token: 'mrp_...'
})

Configuration Options

  • token - Static string or async function returning a token
  • tokenPrefix - Token prefix (default: 'Bearer')
  • headerName - Custom header name (default: 'Authorization')

Making API Requests

You can make requests using either the module methods or the generic request() method:
// Get a project (type-safe)
const project = await client.labrinth.projects_v3.get('sodium')
console.log(project.project_types) // v3 field

// Get multiple projects
const projects = await client.labrinth.projects_v3.getMultiple(['sodium', 'lithium'])

// Get project dependencies
const deps = await client.labrinth.projects_v3.getDependencies('sodium')

Request Options

interface RequestOptions {
  api: 'labrinth' | 'archon' | 'kyros' | string  // API service or custom URL
  version: number                                // API version
  method?: 'GET' | 'POST' | 'PATCH' | 'DELETE'  // HTTP method
  headers?: Record<string, string>               // Custom headers
  body?: unknown                                 // Request body
  params?: Record<string, string>                // Query parameters
  timeout?: number                               // Request timeout
  signal?: AbortSignal                           // Abort signal
  skipAuth?: boolean                             // Skip authentication
}

File Uploads with Progress Tracking

File uploads use XMLHttpRequest for progress tracking (not available via fetch). The upload() method returns an UploadHandle:
interface UploadHandle<T> {
  promise: Promise<T>
  onProgress(callback: (progress: UploadProgress) => void): UploadHandle<T>
  cancel(): void
}
const file = new File([...], 'world.zip')
const path = '/servers/my-server/world.zip'

const uploader = client.kyros.files_v0.uploadFile(path, file, {
  onProgress: ({ progress, loaded, total }) => {
    const percent = Math.round(progress * 100)
    console.log(`Upload progress: ${percent}%`)
  }
})

// Cancel if needed
// uploader.cancel()

await uploader.promise
console.log('Upload complete!')

Upload Modes

  • Single file - { file: File | Blob } sends with Content-Type: application/octet-stream
  • FormData - { formData: FormData } for multipart uploads
Uploads go through the feature chain (auth, retry, etc.). Features can detect uploads via context.metadata.isUpload.

WebSocket Usage

WebSocket support is available on GenericModrinthClient for real-time communication with Modrinth Hosting servers.

Connecting to a Server

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

const client = new GenericModrinthClient({ /* config */ })

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

Connection Flow

1

Fetch JWT authentication

The client fetches a JWT token via archon.servers_v0.getWebSocketAuth()
2

Open WebSocket connection

Opens a wss:// connection to the server
3

Authenticate

Sends { event: 'auth', jwt: token } to authenticate
4

Receive auth confirmation

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

Ready for events

Connection is ready to send commands and receive events

Subscribing to Events

// Subscribe to server stats
const unsubscribe = client.archon.sockets.on(serverId, 'stats', (data) => {
  // data is typed as Archon.Websocket.v0.WSStatsEvent
  console.log('CPU:', data.cpu_percent)
  console.log('Memory:', data.memory_bytes)
})

// Subscribe to logs
client.archon.sockets.on(serverId, 'log', (data) => {
  console.log('[Server]', data.message)
})

// Clean up on unmount
onUnmounted(() => {
  unsubscribe()
  client.archon.sockets.disconnect(serverId)
})

Available Events

  • log - Server console output
  • stats - Server resource usage (CPU, memory, etc.)
  • power-state - Server power state changes
  • uptime - Server uptime updates
  • backup-progress - Backup creation progress
  • installation-result - Mod installation results
  • filesystem-ops - File system operation events
  • new-mod - New mod detected
  • auth-expiring - Authentication expiring soon
  • auth-incorrect - Authentication failed
  • auth-ok - Authentication successful

Sending Commands

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

Auto-Reconnection

The WebSocket client automatically reconnects on unexpected disconnection with exponential backoff:
  • Base delay: 1 second
  • Max delay: 30 seconds
  • Max attempts: 10

TypeScript Types

Types are organized in namespaces that mirror the backend services:
import type { Labrinth, Archon, Kyros, ISO3166 } from '@modrinth/api-client'

// Labrinth types
const project: Labrinth.Projects.v3.Project = await client.labrinth.projects_v3.get('sodium')
const version: Labrinth.Versions.v3.Version = await client.labrinth.versions_v3.get(versionId)

// Archon types
const server: Archon.Servers.v0.Server = await client.archon.servers_v0.get(serverId)
const wsEvent: Archon.Websocket.v0.WSStatsEvent = { /* ... */ }

// Kyros types
const fileInfo: Kyros.Files.v0.FileInfo = await client.kyros.files_v0.get(path)
Types match 1:1 with the backend API responses. They are not reshaped or renamed.

Error Handling

The client throws ModrinthApiError for API errors and ModrinthServerError for server errors:
import { ModrinthApiError, ModrinthServerError } from '@modrinth/api-client'

try {
  const project = await client.labrinth.projects_v3.get('unknown-project')
} catch (error) {
  if (error instanceof ModrinthApiError) {
    console.error('API Error:', error.status, error.message)
    console.error('Response:', error.data)
  } else if (error instanceof ModrinthServerError) {
    console.error('Server Error:', error.message)
  } else {
    console.error('Unknown Error:', error)
  }
}

Error Properties

class ModrinthApiError extends Error {
  status: number           // HTTP status code
  statusText: string       // HTTP status text
  data: unknown           // Response body
  url: string             // Request URL
  message: string         // Error message
}

Features (Middleware)

Features wrap requests in a chain, allowing you to modify requests, add retry logic, or short-circuit requests.

Retry Feature

Automatically retries failed requests with configurable backoff:
import { RetryFeature } from '@modrinth/api-client'

const client = new GenericModrinthClient({
  userAgent: 'my-app/1.0.0',
  features: [
    new RetryFeature({
      maxAttempts: 3,
      backoffStrategy: 'exponential',  // 'exponential' | 'linear' | 'constant'
      initialDelay: 1000,              // 1 second
      maxDelay: 15000,                 // 15 seconds
      retryableStatusCodes: [408, 429, 500, 502, 503, 504],
      retryOnNetworkError: true
    })
  ]
})

Circuit Breaker Feature

Prevents cascade failures by opening circuits after repeated failures:
import { CircuitBreakerFeature, InMemoryCircuitBreakerStorage } from '@modrinth/api-client'

const client = new GenericModrinthClient({
  userAgent: 'my-app/1.0.0',
  features: [
    new CircuitBreakerFeature({
      maxFailures: 3,                  // Open after 3 consecutive failures
      resetTimeout: 30000,             // Try again after 30 seconds
      failureStatusCodes: [500, 502, 503, 504],
      storage: new InMemoryCircuitBreakerStorage()
    })
  ]
})
For Nuxt, use NuxtCircuitBreakerStorage which persists state across SSR/CSR boundaries.

Combining Features

Features are executed in order. Common pattern:
import { 
  GenericModrinthClient,
  AuthFeature,
  RetryFeature,
  CircuitBreakerFeature,
  InMemoryCircuitBreakerStorage
} from '@modrinth/api-client'

const client = new GenericModrinthClient({
  userAgent: 'my-app/1.0.0',
  features: [
    new AuthFeature({ token: 'mrp_...' }),
    new RetryFeature({ maxAttempts: 3 }),
    new CircuitBreakerFeature({ 
      maxFailures: 5,
      storage: new InMemoryCircuitBreakerStorage() 
    })
  ]
})

Custom Base URLs

Override base URLs for staging environments or custom instances:
const client = new GenericModrinthClient({
  userAgent: 'my-app/1.0.0',
  labrinthBaseUrl: 'https://staging-api.modrinth.com/',
  archonBaseUrl: 'https://staging-archon.modrinth.com/',
  features: []
})

// Now requests use staging URLs
await client.request('/project/sodium', { api: 'labrinth', version: 2 })
// -> https://staging-api.modrinth.com/v2/project/sodium
You can also use custom URLs directly in requests:
// One-off custom URL (useful for Kyros nodes)
await client.request('/some-endpoint', {
  api: 'https://eu-lim16.nodes.modrinth.com/',
  version: 0
})