Skip to content

REST API Documentation

This guide covers the MAID v1 REST API for external integrations, including authentication, rate limiting, endpoints, and real-time event streaming.

Overview

The MAID REST API provides programmatic access to server data and administrative functions. It is designed for: - External tools and dashboards - Discord/IRC bots - Monitoring systems - Custom integrations

Base URL: /api/v1

OpenAPI Documentation: - Swagger UI: /api/docs - ReDoc: /api/redoc - OpenAPI JSON: /api/openapi.json

Note: OpenAPI documentation is automatically generated by FastAPI based on the endpoint definitions, Pydantic models, and docstrings in the codebase. No separate OpenAPI specification file is maintained.


Authentication

The API uses API key authentication via the X-API-Key header.

Obtaining an API Key

API keys are generated through the CLI or admin API:

# Generate a new API key
uv run maid api generate-key --name "My Integration" --permissions READ_PUBLIC,READ_PLAYERS

# Output:
# API Key created successfully!
# Key ID: 550e8400-e29b-41d4-a716-446655440000
# Name: My Integration
# Permissions: READ_PUBLIC, READ_PLAYERS
#
# IMPORTANT: Save this key now - it cannot be retrieved later!
# API Key: maid_550e8400_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Using the API Key

Include the key in the X-API-Key header:

curl -X GET "http://localhost:8080/api/v1/players" \
  -H "X-API-Key: maid_550e8400_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

API Key Format

Keys follow the format: maid_{key_id_prefix}_{secret}

  • maid_ - Prefix for identification
  • {key_id_prefix} - First 8 characters of the key UUID
  • {secret} - 32-character secure random token

Permissions

Permission Description
READ_PUBLIC Read public server information
READ_PLAYERS Read player data
WRITE_PLAYERS Modify player data
READ_WORLD Read world data (rooms, NPCs, items)
WRITE_WORLD Modify world data
ADMIN Administrative actions

Permission Combinations: - READ_ALL = READ_PUBLIC + READ_PLAYERS + READ_WORLD - WRITE_ALL = WRITE_PLAYERS + WRITE_WORLD - FULL_ACCESS = All permissions


Rate Limiting

The API implements sliding window rate limiting.

Default Limits

  • Per API Key: 60 requests/minute (configurable per key)
  • Sliding window: 60 seconds

Rate Limit Headers

All responses include rate limit information:

Header Description
X-RateLimit-Limit Maximum requests per window
X-RateLimit-Remaining Remaining requests in window
X-RateLimit-Reset Unix timestamp when limit resets

Rate Limit Exceeded

When rate limited, the API returns:

HTTP/1.1 429 Too Many Requests
Retry-After: 60
X-RateLimit-Remaining: 0

{
  "detail": "Rate limit exceeded"
}

Endpoints

Health Check

GET /api/v1/health

Check API health status. No authentication required.

Response:

{
  "status": "ok",
  "timestamp": "2024-01-15T10:30:00Z",
  "version": "0.1.0",
  "uptime_seconds": 86400
}

Example:

curl http://localhost:8080/api/v1/health

Players API

List Players

GET /api/v1/players

List players with pagination, filtering, and sorting.

Required Permission: READ_PLAYERS

Query Parameters:

Parameter Type Default Description
page int 1 Page number (1-indexed)
page_size int 50 Items per page (1-100)
search str None Search by username or email
status str None Filter by status (active, banned, suspended)
role str None Filter by role
online_only bool false Only show online players
sort_by str username Sort field (username, created_at, last_login, status)
sort_order str asc Sort order (asc, desc)

Response:

{
  "players": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "username": "player1",
      "status": "active",
      "role": "user",
      "is_online": true,
      "created_at": "2024-01-15T10:30:00Z",
      "last_login": "2024-01-20T15:45:00Z"
    }
  ],
  "total": 150,
  "page": 1,
  "page_size": 50,
  "has_next": true,
  "has_previous": false
}

Example:

curl "http://localhost:8080/api/v1/players?online_only=true&page_size=10" \
  -H "X-API-Key: maid_xxx..."

Get Player Details

GET /api/v1/players/{player_id}

Get detailed information about a specific player.

Required Permission: READ_PLAYERS

Response:

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "username": "player1",
  "email": "player@example.com",
  "status": "active",
  "role": "user",
  "is_online": true,
  "created_at": "2024-01-15T10:30:00Z",
  "last_login": "2024-01-20T15:45:00Z",
  "login_attempts": 0,
  "locked_until": null,
  "current_character": "char-123",
  "current_location": "Town Square",
  "metadata": {}
}

Update Player

PATCH /api/v1/players/{player_id}

Update player account data.

Required Permission: WRITE_PLAYERS

Request Body:

{
  "email": "newemail@example.com",
  "metadata": {"notes": "VIP player"}
}

Response:

{
  "player_id": "550e8400-e29b-41d4-a716-446655440000",
  "username": "player1",
  "updated_fields": ["email", "metadata"],
  "success": true,
  "message": "Updated 2 field(s)"
}

Kick Player

POST /api/v1/players/{player_id}/kick

Disconnect an active player session.

Required Permission: ADMIN

Request Body:

{
  "reason": "Server maintenance"
}

Response:

{
  "player_id": "550e8400-e29b-41d4-a716-446655440000",
  "username": "player1",
  "reason": "Server maintenance",
  "success": true,
  "message": "Player kicked successfully"
}

Ban Player

POST /api/v1/players/{player_id}/ban

Ban a player account.

Required Permission: ADMIN

Request Body:

{
  "reason": "Violation of community guidelines",
  "duration_hours": 24
}

Response:

{
  "player_id": "550e8400-e29b-41d4-a716-446655440000",
  "username": "player1",
  "reason": "Violation of community guidelines",
  "banned_until": "2024-01-16T10:30:00Z",
  "success": true,
  "message": "Player banned successfully"
}

Unban Player

DELETE /api/v1/players/{player_id}/ban

Remove a ban from a player account.

Required Permission: ADMIN

Response:

{
  "player_id": "550e8400-e29b-41d4-a716-446655440000",
  "username": "player1",
  "success": true,
  "message": "Player unbanned successfully"
}

World API

List Rooms

GET /api/v1/world/rooms

List all rooms with optional filtering.

Required Permission: READ_WORLD

Query Parameters:

Parameter Type Default Description
area_id str None Filter by area ID
offset int 0 Pagination offset
limit int 50 Items per page (1-100)

Response:

{
  "items": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "name": "Town Square",
      "area_id": "660e8400-e29b-41d4-a716-446655440001",
      "area_name": "Town",
      "entity_count": 5
    }
  ],
  "total": 500,
  "offset": 0,
  "limit": 50,
  "has_more": true
}

Get Room Details

GET /api/v1/world/rooms/{room_id}

Get detailed information about a specific room.

Required Permission: READ_WORLD

Response:

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "name": "Town Square",
  "description": "The central square of the town.",
  "area_id": "660e8400-e29b-41d4-a716-446655440001",
  "area_name": "Town",
  "exits": [
    {
      "direction": "north",
      "destination_id": "770e8400-e29b-41d4-a716-446655440002",
      "destination_name": "Market Street",
      "is_locked": false
    }
  ],
  "entity_count": 5,
  "properties": {
    "terrain": "cobblestone",
    "light_level": "bright"
  }
}

Get Room Contents

GET /api/v1/world/rooms/{room_id}/contents

Get the contents of a room (players, NPCs, items).

Required Permission: READ_WORLD

Response:

{
  "room_id": "550e8400-e29b-41d4-a716-446655440000",
  "room_name": "Town Square",
  "players": [
    {"id": "aaa...", "name": "Player1", "entity_type": "player", "description": ""}
  ],
  "npcs": [
    {"id": "bbb...", "name": "Guard", "entity_type": "npc", "description": "A town guard"}
  ],
  "items": [
    {"id": "ccc...", "name": "Sword", "entity_type": "item", "description": "A rusty sword"}
  ],
  "total_count": 3
}

List NPCs

GET /api/v1/world/npcs

List all NPCs with optional filtering.

Required Permission: READ_WORLD

Query Parameters:

Parameter Type Description
room_id str Filter by room ID
npc_type str Filter by NPC type
is_hostile bool Filter by hostility
offset int Pagination offset
limit int Items per page

List Items

GET /api/v1/world/items

List all items (on ground) with optional filtering.

Required Permission: READ_WORLD

Query Parameters:

Parameter Type Description
room_id str Filter by room ID
item_type str Filter by item type
is_takeable bool Filter by takeability
offset int Pagination offset
limit int Items per page

List Areas

GET /api/v1/world/areas

List all areas/zones.

Required Permission: READ_WORLD


Statistics API

Server Statistics

GET /api/v1/stats/server

Get server statistics.

Required Permission: READ_PUBLIC

Response:

{
  "uptime_seconds": 86400,
  "tick_rate": 4.0,
  "current_tick": 345600,
  "connected_players": 42,
  "memory_usage_mb": 256.5,
  "cpu_usage_percent": null,
  "version": "0.1.0",
  "start_time": "2024-01-14T10:30:00Z"
}

Player Statistics

GET /api/v1/stats/players

Get player statistics.

Required Permission: READ_PLAYERS

Response:

{
  "online_count": 42,
  "peak_players_today": 67,
  "peak_players_all_time": 150,
  "new_registrations_today": 5,
  "new_registrations_this_week": 28,
  "total_accounts": 1234,
  "active_sessions": 42
}

Economy Statistics

GET /api/v1/stats/economy

Get economy statistics.

Required Permission: READ_WORLD

Response:

{
  "total_currency_in_circulation": 1500000,
  "transactions_today": 342,
  "transactions_this_week": 2156,
  "average_player_wealth": 1216.5,
  "median_player_wealth": 500,
  "top_player_wealth": 50000,
  "currency_name": "gold"
}

Combat Statistics

GET /api/v1/stats/combat

Get combat statistics.

Required Permission: READ_WORLD

Response:

{
  "battles_today": 156,
  "battles_this_week": 1089,
  "player_deaths_today": 23,
  "player_deaths_this_week": 178,
  "mob_kills_today": 412,
  "mob_kills_this_week": 2891,
  "total_damage_dealt_today": 145678,
  "total_damage_dealt_this_week": 1023456,
  "pvp_battles_today": 12,
  "pvp_battles_this_week": 67
}

Command Statistics (Admin Only)

GET /api/v1/stats/commands

Get command usage statistics.

Required Permission: ADMIN

Response:

{
  "total_commands_executed": 50000,
  "commands_executed_today": 1234,
  "commands_executed_this_week": 8567,
  "unique_commands_used": 45,
  "most_used_commands": [
    {
      "command": "look",
      "total_invocations": 15000,
      "invocations_today": 500,
      "invocations_this_week": 3200,
      "average_execution_time_ms": 2.5,
      "error_count": 0
    }
  ],
  "error_rate_percent": 0.5
}

AI Pricing API

Get AI Pricing

GET /admin/ai/pricing

Get AI provider pricing configuration.

Required Permission: ADMIN

Response:

{
  "providers": [
    {
      "provider": "anthropic",
      "model": "claude-sonnet-4-20250514",
      "input_cost_per_1k": 0.003,
      "output_cost_per_1k": 0.015
    }
  ]
}

Update AI Pricing

PUT /admin/ai/pricing

Update pricing for a model/provider.

Required Permission: ADMIN

Request Body:

{
  "provider": "anthropic",
  "model": "claude-sonnet-4-20250514",
  "input_cost_per_1k": 0.003,
  "output_cost_per_1k": 0.015
}

Response:

{
  "success": true,
  "message": "Pricing updated for anthropic/claude-sonnet-4-20250514"
}

Player Client Auth API

Authentication endpoints for player game clients (not to be confused with the API key auth used by external integrations).

Player Login

POST /api/v1/auth/login

Authenticate a player and receive JWT tokens.

Request Body:

{
  "username": "player1",
  "password": "secret"
}

Response (200):

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "bearer",
  "expires_in": 3600
}

Refresh Player Token

POST /api/v1/auth/refresh

Refresh a player's access token.

Request Body:

{
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Response (200):

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "bearer",
  "expires_in": 3600
}

Player Logout

POST /api/v1/auth/logout

Revoke the current player token.

Headers:

Authorization: Bearer <access_token>

Response (200):

{
  "message": "Successfully logged out"
}

Get Current Player

GET /api/v1/auth/me

Get information about the currently authenticated player.

Headers:

Authorization: Bearer <access_token>

Response (200):

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "username": "player1",
  "email": "player@example.com",
  "created_at": "2024-01-15T10:30:00Z",
  "last_login": "2024-01-20T15:45:00Z"
}

List Player Characters

GET /api/v1/characters

List the authenticated player's characters.

Headers:

Authorization: Bearer <access_token>

Response (200):

{
  "characters": [
    {
      "id": "660e8400-e29b-41d4-a716-446655440001",
      "name": "Gandalf",
      "class": "Wizard",
      "level": 20
    }
  ]
}

Create Character

POST /api/v1/characters

Create a new character for the authenticated player.

Headers:

Authorization: Bearer <access_token>

Request Body:

{
  "name": "Aragorn",
  "class": "Ranger"
}

Response (201):

{
  "id": "770e8400-e29b-41d4-a716-446655440002",
  "name": "Aragorn",
  "class": "Ranger",
  "level": 1,
  "message": "Character created successfully"
}

Get Character Details

GET /api/v1/characters/{character_id}

Get detailed information about a specific character.

Headers:

Authorization: Bearer <access_token>

Response (200):

{
  "id": "660e8400-e29b-41d4-a716-446655440001",
  "name": "Gandalf",
  "class": "Wizard",
  "level": 20,
  "stats": {},
  "location": "Town Square"
}

Update Player Settings

PUT /api/v1/settings

Update the authenticated player's settings.

Headers:

Authorization: Bearer <access_token>

Request Body:

{
  "color_mode": "ansi256",
  "screen_width": 80,
  "brief_mode": false
}

Response (200):

{
  "success": true,
  "message": "Settings updated"
}

Observability Endpoints

Operational observability endpoints served on a separate internal port (9090), not the main API port. These are intended for infrastructure tooling (Prometheus, Kubernetes, load balancers) and should not be exposed publicly.

Prometheus Metrics

GET http://localhost:9090/metrics

Prometheus metrics scrape endpoint. Returns metrics in Prometheus exposition format.

Example:

curl http://localhost:9090/metrics

Health Check

GET http://localhost:9090/healthz

Basic health check. Returns 200 if the process is running.

Response (200):

{"status": "ok"}

Readiness Check

GET http://localhost:9090/readyz

Readiness check. Returns 200 when the server is ready to accept player traffic (all content packs loaded, network listeners active).

Response (200):

{"status": "ready"}

Liveness Check

GET http://localhost:9090/livez

Liveness check. Returns 200 when the tick loop is actively running.

Response (200):

{"status": "alive", "last_tick_ms_ago": 250}

Admin API

Broadcast Message

POST /api/v1/admin/broadcast

Broadcast a message to all connected players.

Required Permission: ADMIN

Request Body:

{
  "message": "Server maintenance in 10 minutes",
  "prefix": "[ADMIN]"
}

Response:

{
  "success": true,
  "recipients": 42,
  "message": "[ADMIN] Server maintenance in 10 minutes"
}

Shutdown Server

POST /api/v1/admin/shutdown

Initiate graceful server shutdown.

Required Permission: ADMIN

Request Body:

{
  "delay_seconds": 300,
  "reason": "Scheduled maintenance",
  "broadcast_warning": true
}

Response:

{
  "success": true,
  "message": "Shutdown scheduled: Scheduled maintenance",
  "scheduled_at": "2024-01-15T10:35:00Z"
}

Reload Configuration

POST /api/v1/admin/reload-config

Reload server configuration from files.

Required Permission: ADMIN

Response:

{
  "success": true,
  "message": "Configuration reloaded successfully",
  "reloaded_sections": ["engine", "world"]
}

List API Keys

GET /api/v1/admin/api-keys

List all API keys.

Required Permission: ADMIN

Query Parameters:

Parameter Type Default Description
include_inactive bool false Include revoked keys

Response:

{
  "keys": [
    {
      "key_id": "550e8400-e29b-41d4-a716-446655440000",
      "name": "My Integration",
      "permissions": ["READ_PUBLIC", "READ_PLAYERS"],
      "created_at": "2024-01-15T10:30:00Z",
      "expires_at": null,
      "last_used": "2024-01-20T15:45:00Z",
      "created_by": "admin",
      "is_active": true,
      "rate_limit_rpm": 60
    }
  ],
  "total": 5
}

Create API Key

POST /api/v1/admin/api-keys

Generate a new API key.

Required Permission: ADMIN

Request Body:

{
  "name": "Bot Integration",
  "permissions": ["READ_PUBLIC", "READ_PLAYERS"],
  "expires_in_days": 365,
  "rate_limit_rpm": 120
}

Response (201):

{
  "key_id": "660e8400-e29b-41d4-a716-446655440001",
  "name": "Bot Integration",
  "permissions": ["READ_PUBLIC", "READ_PLAYERS"],
  "created_at": "2024-01-15T10:30:00Z",
  "expires_at": "2025-01-15T10:30:00Z",
  "last_used": null,
  "created_by": "admin-key",
  "is_active": true,
  "rate_limit_rpm": 120,
  "raw_key": "maid_660e8400_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}

Important: The raw_key is only returned once. Store it securely.

Revoke API Key

DELETE /api/v1/admin/api-keys/{key_id}

Revoke an API key.

Required Permission: ADMIN

Response: 204 No Content


Admin User Management

Manage admin panel users. All endpoints require the ADMIN admin role. Only SUPERADMIN users can create, modify, or delete users with ADMIN or SUPERADMIN roles.

Admin Roles (hierarchical — each role includes permissions of lower roles):

Role Value Description
VIEWER 10 Read-only access to dashboards and stats
MODERATOR 20 Can moderate players, kick, mute
BUILDER 30 Can create/edit world content
ADMIN 40 Full admin access, manage users
SUPERADMIN 50 System-level access, configuration changes

List Admin Users

GET /admin/users

List all admin users.

Required Role: ADMIN

Get Admin User

GET /admin/users/{user_id}

Get details for a specific admin user.

Required Role: ADMIN

Create Admin User

POST /admin/users

Create a new admin user.

Required Role: ADMIN (only SUPERADMIN can create ADMIN or SUPERADMIN users)

Request Body:

{
  "username": "newadmin",
  "password": "securepassword",
  "role": "MODERATOR"
}

Update Admin User

PUT /admin/users/{user_id}

Update an admin user's role or status.

Required Role: ADMIN (only SUPERADMIN can modify ADMIN or SUPERADMIN users)

Note: Users cannot demote themselves.

Delete Admin User

DELETE /admin/users/{user_id}

Delete an admin user.

Required Role: ADMIN (only SUPERADMIN can delete ADMIN or SUPERADMIN users)

Note: Users cannot delete themselves.


WebSocket Events

WebSocket /api/v1/events

Stream real-time game events.

Query Parameters:

Parameter Type Description
api_key str Optional API key for authentication
types str Comma-separated event types to subscribe to

Example Connection:

const ws = new WebSocket(
  'ws://localhost:8080/api/v1/events?api_key=maid_xxx&types=player_login,chat_message'
);

ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  console.log('Event:', data.type, data.data);
};

Event Types:

Type Description
player_login Player connected
player_logout Player disconnected
player_command Player executed a command
chat_message Chat message sent
system_message System message
combat Combat started
damage Damage dealt
death Entity died
respawn Entity respawned
world_event World event occurred
room_enter Entity entered a room
room_leave Entity left a room
entity_created Entity created
entity_destroyed Entity destroyed
component_added Component added to entity
component_removed Component removed from entity
tick Game tick (use sparingly)

Event Message Format:

{
  "type": "player_login",
  "timestamp": "2024-01-15T10:30:00Z",
  "data": {
    "session_id": "xxx",
    "player_id": "yyy"
  }
}

Client Messages:

// Ping (server responds with pong)
{"type": "ping"}

// Update subscription
{"type": "subscribe", "types": ["combat", "chat_message"]}

WebSocket Protocol Extensions

The WebSocket protocol supports additional message types for richer client integration:

Request-Response Correlation:

Messages may include an optional requestId field. When present, the server echoes it back in the corresponding response, enabling clients to correlate requests with responses:

{"type": "subscribe", "types": ["combat"], "requestId": "abc-123"}

// Server response:
{"type": "subscribed", "types": ["combat"], "requestId": "abc-123"}

GMCP Data Subscription:

Use the subscribe message type to subscribe to GMCP data channels for structured game data:

{"type": "subscribe", "channels": ["Char.Vitals", "Room.Info"]}

Batched GMCP Updates:

The server may send a gmcp_batch message containing multiple GMCP updates in a single frame, reducing overhead for high-frequency data:

{
  "type": "gmcp_batch",
  "updates": [
    {"package": "Char.Vitals", "data": {"hp": 95, "mp": 40}},
    {"package": "Room.Info", "data": {"name": "Town Square"}}
  ]
}

Reconnect State Sync:

On reconnect, the server sends a sync_complete message containing a full state snapshot so the client can restore its view without requesting each piece individually:

{
  "type": "sync_complete",
  "data": {
    "character": { "hp": 100, "mp": 50, "location": "Town Square" },
    "room": { "name": "Town Square", "exits": ["north", "east"] },
    "active_effects": []
  }
}

Error Responses

All errors follow a consistent format:

{
  "detail": "Error message describing what went wrong"
}

HTTP Status Codes:

Code Description
400 Bad Request - Invalid input
401 Unauthorized - API key required
403 Forbidden - Insufficient permissions
404 Not Found - Resource does not exist
409 Conflict - Resource already exists
429 Too Many Requests - Rate limit exceeded
500 Internal Server Error
503 Service Unavailable - Engine not ready

Example: curl Commands

Health Check

curl http://localhost:8080/api/v1/health

List Online Players

curl "http://localhost:8080/api/v1/players?online_only=true" \
  -H "X-API-Key: maid_xxx_yyy"

Get Player Details

curl "http://localhost:8080/api/v1/players/550e8400-e29b-41d4-a716-446655440000" \
  -H "X-API-Key: maid_xxx_yyy"

Ban a Player

curl -X POST "http://localhost:8080/api/v1/players/550e8400.../ban" \
  -H "X-API-Key: maid_xxx_yyy" \
  -H "Content-Type: application/json" \
  -d '{"reason": "Cheating", "duration_hours": 24}'

Broadcast Message

curl -X POST "http://localhost:8080/api/v1/admin/broadcast" \
  -H "X-API-Key: maid_xxx_yyy" \
  -H "Content-Type: application/json" \
  -d '{"message": "Server restart in 5 minutes"}'

Create API Key

curl -X POST "http://localhost:8080/api/v1/admin/api-keys" \
  -H "X-API-Key: maid_xxx_yyy" \
  -H "Content-Type: application/json" \
  -d '{"name": "Dashboard", "permissions": ["READ_PUBLIC", "READ_PLAYERS"]}'