Skip to content

MAID Admin API Reference

The MAID Admin API provides a comprehensive REST/WebSocket interface for server administration, monitoring, and management. All endpoints are prefixed with /admin.

Admin Web UI

MAID includes a React-based admin web interface that provides a visual dashboard for server management.

Building the Admin UI

The admin frontend is located at packages/maid-engine/admin_frontend/. To build it:

# Navigate to the admin frontend directory
cd packages/maid-engine/admin_frontend

# Install dependencies
npm install

# Build for production
npm run build

# For development with hot reload (connects to API on localhost:8080)
npm run dev

Accessing the Admin UI

Once built, the admin UI is automatically served by the MAID server:

  • Production: http://your-server:8080/admin-ui/
  • Development: http://localhost:3000/ (with Vite dev server)

The production build is served from packages/maid-engine/admin_frontend/dist/ via FastAPI's StaticFiles mount.

UI Features

  • Dashboard: Real-time server metrics and performance graphs
  • Players: View and manage online players, ban/kick functionality
  • Entities: Browse and edit game entities and their components
  • World: View room topology and navigate the game world
  • Logs: Search and stream server logs in real-time
  • Config: View and modify server configuration

Table of Contents


Authentication

The Admin API uses JWT (JSON Web Tokens) for authentication. Tokens can be provided via:

  • Authorization: Bearer <token> header (recommended for API clients)
  • HTTP-only cookies (maid_access_token, maid_refresh_token) set automatically on login (used by the browser-based admin UI)

When using cookie-based authentication, all state-changing requests (POST, PUT, PATCH, DELETE) must include an X-CSRF-Token header matching the maid_csrf_token cookie. The login endpoint is exempt from CSRF. Bearer-header authentication bypasses CSRF entirely.

Login

POST /admin/auth/login

Authenticate with username and password. Returns user info in the response body and sets auth cookies for browser clients.

Request Body:

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

Response (200):

{
  "status": "ok",
  "user": {
    "user_id": "550e8400-e29b-41d4-a716-446655440000",
    "username": "admin",
    "email": "admin@example.com",
    "role": "ADMIN",
    "permissions": ["view", "edit", "admin"]
  }
}

Note: Login also sets HTTP-only cookies (maid_access_token, maid_refresh_token) and a non-HTTP-only maid_csrf_token cookie. The secure flag is set automatically when the request arrives over HTTPS.

Errors: - 401 - Invalid credentials or insufficient privileges - 429 - Rate limit exceeded

Refresh Token

POST /admin/auth/refresh

Exchange a refresh token for new access and refresh tokens. The refresh token can be provided in the request body or via the maid_refresh_token cookie.

Request Body (optional):

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

Response (200):

{
  "status": "ok"
}

Note: Refresh rotates all auth cookies. The old refresh token is revoked on use.

Errors: - 401 - No refresh token provided, or token is invalid/expired/revoked, or account no longer valid - 403 - Admin access has been revoked (account role no longer qualifies) - 429 - Rate limit exceeded

Logout

POST /admin/auth/logout

Revoke the current access token and (optionally) the refresh token. The refresh token can be provided in the body or via the maid_refresh_token cookie.

Request Body (optional):

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

Response (204): No Content

Auth cookies are cleared automatically for browser-based clients.

Errors: - 401 - Not authenticated

Get Current User

GET /admin/auth/me

Get information about the currently authenticated user.

Response (200):

{
  "user_id": "550e8400-e29b-41d4-a716-446655440000",
  "username": "admin",
  "email": "admin@example.com",
  "role": "ADMIN",
  "permissions": ["view", "edit", "admin"]
}

Admin Panel Roles

Panel roles are independent from the account's in-game role. Panel access does not grant builder or game-administrator commands, and an in-game administrator cannot log in to the panel unless a panel role is assigned separately.

Role Description Capabilities
VIEWER Read-only access View entities, logs, metrics
MODERATOR Moderation Kick/mute players, view reports
BUILDER Panel content editing Create/modify content through the panel
ADMIN Administration Manage players, reload packs
SUPERADMIN Full access All operations including config

User Management

Manage the panel-access axis of shared player accounts. The Administrators tab exposes these operations; existing player accounts can be granted panel access from the Players tab.

For the first panel administrator, set MAID_BOOTSTRAP_ADMIN_PASSWORD before starting the server. Optional MAID_BOOTSTRAP_ADMIN_USERNAME and MAID_BOOTSTRAP_ADMIN_EMAIL values select the account. Bootstrap grants panel SUPERADMIN only and preserves any existing in-game role.

List Admin Users

GET /admin/users

List all admin users.

Required Role: ADMIN

Response (200):

{
  "users": [
    {
      "user_id": "550e8400-e29b-41d4-a716-446655440000",
      "username": "admin",
      "email": "admin@example.com",
      "role": "ADMIN",
      "status": "active",
      "created_at": "2024-01-01T00:00:00Z",
      "last_login": "2024-01-15T10:30:00Z"
    }
  ],
  "total": 5,
  "requested_by": "admin"
}

Get Admin User

GET /admin/users/{user_id}

Get details for a specific admin user.

Required Role: ADMIN

Response (200):

{
  "user_id": "550e8400-e29b-41d4-a716-446655440000",
  "username": "admin",
  "email": "admin@example.com",
  "role": "ADMIN",
  "status": "active",
  "created_at": "2024-01-01T00:00:00Z",
  "last_login": "2024-01-15T10:30:00Z"
}

Errors: - 404 - User not found

Create Admin User

POST /admin/users

Create one account identity with a regular in-game user role and the requested panel role. The account can also be used to play, but receives no elevated in-game authority from this endpoint.

Required Role: ADMIN (SUPERADMIN required to grant ADMIN or SUPERADMIN)

Request Body:

{
  "username": "newadmin",
  "email": "newadmin@example.com",
  "password": "securepassword",
  "role": "MODERATOR"
}

Response (201):

{
  "user_id": "660e8400-e29b-41d4-a716-446655440001",
  "username": "newadmin",
  "email": "newadmin@example.com",
  "role": "MODERATOR",
  "message": "Admin user created successfully"
}

Update Admin User

PUT /admin/users/{user_id}

Update an account's panel role or status. This route can grant panel access to an existing player account and never changes its in-game role.

Required Role: ADMIN (SUPERADMIN required to assign ADMIN or SUPERADMIN)

Request Body:

{
  "role": "BUILDER",
  "status": "active"
}

Response (200):

{
  "user_id": "660e8400-e29b-41d4-a716-446655440001",
  "username": "newadmin",
  "role": "BUILDER",
  "status": "active",
  "message": "Admin user updated successfully"
}

Remove Admin Panel Access

DELETE /admin/users/{user_id}

Clear the account's panel role. The shared account identity, in-game role, and owned characters are preserved.

Required Role: ADMIN (SUPERADMIN required for ADMIN/SUPERADMIN panel users)

Response (200):

{
  "user_id": "660e8400-e29b-41d4-a716-446655440001",
  "message": "Panel access removed for newadmin; account preserved"
}


Server Stats

Get Server Stats

GET /admin/stats

Quick server status overview.

Required Role: VIEWER

Response (200):

{
  "status": "online",
  "entities": 2345,
  "systems": 12,
  "content_packs": 3,
  "requested_by": "admin"
}


Dashboard

Real-time server metrics and monitoring.

Get All Metrics

GET /admin/dashboard/

Get comprehensive dashboard metrics.

Required Role: VIEWER

Response (200):

{
  "timestamp": "2024-01-15T10:30:00Z",
  "server": {
    "uptime_seconds": 3600.5,
    "uptime_formatted": "1h 0m 0s",
    "tick_rate": 4.0,
    "current_tick": 14402,
    "average_tick_time_ms": 2.5,
    "max_tick_time_ms": 15.3,
    "tick_overruns": 0,
    "memory_usage_mb": 128.5,
    "cpu_percent": 5.2,
    "content_packs_loaded": 3,
    "engine_state": "RUNNING"
  },
  "players": {
    "online_count": 15,
    "peak_today": 42,
    "total_accounts": 1250,
    "active_sessions": 15,
    "average_session_duration_minutes": 45.5
  },
  "world": {
    "entity_count": 5000,
    "room_count": 250,
    "area_count": 10,
    "npc_count": 300,
    "item_count": 1500,
    "player_entity_count": 15,
    "system_count": 12,
    "events_processed": 125000
  },
  "ai": {
    "total_cost": 1.25,
    "total_tokens": 50000,
    "costs_by_model": {},
    "active_providers": [],
    "ai_player_count": 0,
    "ai_player_active": 0,
    "total_violations": 0,
    "total_ai_actions": 0,
    "violations_by_type": {}
  },
  "persistence": {
    "dirty_count": 0,
    "last_save_time": null,
    "save_interval": 0.0,
    "persisted_entity_count": 0
  },
  "content_packs": ["maid-stdlib", "maid-classic-rpg"],
  "recent_events": []
}

Get Server Metrics

GET /admin/dashboard/server

Get server-specific metrics including CPU, memory, and tick performance.

Required Role: VIEWER

Get Player Metrics

GET /admin/dashboard/players

Get player statistics and session information.

Required Role: VIEWER

Get World Metrics

GET /admin/dashboard/world

Get world state metrics including entity counts and area information.

Required Role: VIEWER

Get Metrics History

POST /admin/dashboard/metrics/history

Query historical time-series data for dashboard metrics.

Required Role: VIEWER

Request Body:

{
  "start_time": "2024-01-15T00:00:00Z",
  "end_time": "2024-01-15T23:59:59Z",
  "metric_types": ["server", "players"],
  "resolution_seconds": 300
}


Entity Management

Note: Entity management routes are defined in maid-stdlib (not the core admin router) and are mounted by content packs at runtime. They may not be available if no content pack registers them.

CRUD operations for game entities.

List Entities

GET /admin/entities/

List entities with filtering and pagination.

Required Role: VIEWER

Query Parameters: | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | page | int | 1 | Page number (min 1) | | page_size | int | 50 | Items per page (1–500) | | tag | string | — | Filter entities by tag | | component_type | string | — | Filter by component type name | | search | string | — | Case-insensitive search across entity IDs, tags, and component data |

Response (200):

{
  "items": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "tags": ["weapon", "magic"],
      "components": [
        {
          "type": "ItemComponent",
          "data": {"weight": 5.0, "value": 1000}
        },
        {
          "type": "DamageComponent",
          "data": {"damage_type": "slashing", "damage_dice": "2d6+3"}
        }
      ],
      "created_at": "2024-01-15T10:30:00Z",
      "updated_at": "2024-01-15T10:30:00Z"
    }
  ],
  "total": 100,
  "page": 1,
  "page_size": 50,
  "has_more": true
}

Errors: - 401 - Not authenticated - 403 - Insufficient permissions - 503 - Engine not available

List Component Types

GET /admin/entities/types

List all registered component type names.

Required Role: VIEWER

Response (200):

["HealthComponent", "InventoryComponent", "PositionComponent"]

Get Entity

GET /admin/entities/{entity_id}

Get detailed information about a specific entity.

Required Role: VIEWER

Response (200):

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "tags": ["weapon", "magic"],
  "components": [
    {
      "type": "ItemComponent",
      "data": {"weight": 5.0, "value": 1000}
    },
    {
      "type": "DamageComponent",
      "data": {"damage_type": "slashing", "damage_dice": "2d6+3"}
    }
  ],
  "created_at": "2024-01-15T10:30:00Z",
  "updated_at": "2024-01-15T10:30:00Z"
}

Errors: - 400 - Invalid entity ID format - 404 - Entity not found

Create Entity

POST /admin/entities/

Create a new entity.

Required Role: BUILDER

Request Body:

{
  "tags": ["weapon"],
  "components": [
    {
      "type": "ItemComponent",
      "data": {"weight": 3.0, "value": 50}
    }
  ]
}

Tags are lowercased and deduplicated. Max 100 tags, each up to 64 characters (alphanumeric, -, _ only). Component types must start with an uppercase letter and be valid Python identifiers.

Response (201):

{
  "id": "770e8400-e29b-41d4-a716-446655440002",
  "message": "Entity created successfully"
}

Errors: - 400 - Unknown component type, invalid component data, or validation error

Delete Entity

DELETE /admin/entities/{entity_id}

Delete an entity from the world.

Required Role: ADMIN

Response (200):

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "message": "Entity deleted successfully"
}

Errors: - 400 - Invalid entity ID format - 404 - Entity not found

Update Component

PUT /admin/entities/{entity_id}/components/{component_type}

Update a component's data on an entity. Only fields present in data are updated.

Required Role: BUILDER

Request Body:

{
  "data": {
    "damage_dice": "3d6+5"
  }
}

Response (200):

{
  "entity_id": "550e8400-e29b-41d4-a716-446655440000",
  "component_type": "DamageComponent",
  "message": "Component updated successfully"
}

Errors: - 400 - Invalid entity ID, unknown field, or validation error - 404 - Entity or component not found

Add Component

POST /admin/entities/{entity_id}/components

Add a new component to an entity.

Required Role: BUILDER

Request Body:

{
  "type": "HealthComponent",
  "data": {
    "maximum": 100,
    "current": 100
  }
}

Response (201):

{
  "entity_id": "550e8400-e29b-41d4-a716-446655440000",
  "component_type": "HealthComponent",
  "message": "Component added successfully"
}

Errors: - 400 - Unknown component type or validation error - 404 - Entity not found - 409 - Component already exists on entity

Remove Component

DELETE /admin/entities/{entity_id}/components/{component_type}

Remove a component from an entity.

Required Role: BUILDER

Response (200):

{
  "entity_id": "550e8400-e29b-41d4-a716-446655440000",
  "component_type": "HealthComponent",
  "message": "Component removed successfully"
}

Errors: - 400 - Invalid entity ID - 404 - Unknown component type, or entity does not have component

Add Tag

POST /admin/entities/{entity_id}/tags/{tag}

Add a tag to an entity.

Required Role: BUILDER

Response (200):

{
  "message": "Tag 'weapon' added to entity 550e8400-..."
}

Errors: - 404 - Entity not found

Remove Tag

DELETE /admin/entities/{entity_id}/tags/{tag}

Remove a tag from an entity.

Required Role: BUILDER

Response (200):

{
  "message": "Tag 'weapon' removed from entity 550e8400-..."
}

Errors: - 404 - Entity not found, or entity does not have tag


Player Management

Note: Player management routes are defined in maid-stdlib and mounted by content packs at runtime.

Manage player accounts and sessions.

List Players

GET /admin/players/

List player accounts with filtering.

Required Role: MODERATOR

Query Parameters: | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | page | int | 1 | Page number (min 1) | | page_size | int | 50 | Items per page (1–100) | | search | string | — | Search by username or email (case-insensitive) | | status | string | — | Filter by account status (e.g., "active", "banned") | | role | string | — | Filter by account role (e.g., "user", "admin") | | online_only | bool | false | Only show online players |

Invalid status or role filter values are silently ignored.

Response (200):

{
  "players": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "username": "player1",
      "email": "player1@example.com",
      "status": "active",
      "role": "user",
      "created_at": "2024-01-01T00:00:00Z",
      "last_login": "2024-01-15T10:30:00Z",
      "is_online": true,
      "login_attempts": 0,
      "locked_until": null,
      "metadata": {}
    }
  ],
  "total": 1234,
  "page": 1,
  "page_size": 50,
  "has_next": true,
  "has_previous": false
}

Errors: - 503 - Account manager not available

Get Player Details

GET /admin/players/{account_id}

Get detailed information about a player.

Required Role: MODERATOR

Response (200):

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "username": "player1",
  "email": "player1@example.com",
  "status": "active",
  "role": "user",
  "created_at": "2024-01-01T00:00:00Z",
  "last_login": "2024-01-15T10:30:00Z",
  "is_online": true,
  "login_attempts": 0,
  "locked_until": null,
  "metadata": {}
}

Errors: - 400 - Invalid account ID format - 404 - Account not found

Get Player Characters

GET /admin/players/{account_id}/characters

List all characters belonging to a player.

Required Role: MODERATOR

Response (200):

{
  "account_id": "550e8400-e29b-41d4-a716-446655440000",
  "username": "player1",
  "characters": [
    {
      "id": "660e8400-e29b-41d4-a716-446655440001",
      "name": "Thorin",
      "level": 5,
      "class_name": "warrior",
      "location": "Town Square",
      "created_at": "2024-01-01T00:00:00Z",
      "last_played": "2024-01-15T10:00:00Z"
    }
  ],
  "total": 1
}

Ban Player

POST /admin/players/{account_id}/ban

Ban a player account. If the player is online, their session is disconnected.

Required Role: MODERATOR

Cannot ban ADMIN or SYSOP accounts unless the acting admin is SUPERADMIN.

Request Body:

{
  "reason": "Violation of terms of service",
  "duration_hours": 24
}

Field Type Required Description
reason string Yes Ban reason (1–500 characters)
duration_hours int | null No Hours until ban expires. null = permanent ban. Must be ≥ 1.

Response (200):

{
  "account_id": "550e8400-...",
  "username": "player1",
  "reason": "Violation of terms of service",
  "banned_until": "2024-01-16T10:30:00Z",
  "message": "Player banned successfully"
}

Errors: - 400 - Invalid account ID format - 403 - Cannot ban this account (protected role) - 404 - Account not found

Unban Player

POST /admin/players/{account_id}/unban

Remove a ban from a player account.

Required Role: MODERATOR

Response (200):

{
  "account_id": "550e8400-...",
  "username": "player1",
  "message": "Player unbanned successfully"
}

Errors: - 400 - Invalid account ID format, or account is not banned - 404 - Account not found

Kick Player

POST /admin/players/{account_id}/kick

Disconnect a player's current session. The player can reconnect immediately.

Required Role: MODERATOR

Request Body:

{
  "reason": "Server maintenance"
}

Field Type Default Description
reason string "Disconnected by administrator" Kick reason (max 200 characters)

Response (200):

{
  "account_id": "550e8400-...",
  "username": "player1",
  "reason": "Server maintenance",
  "message": "Player kicked successfully"
}

Errors: - 400 - Invalid account ID format, or player is not online - 404 - Account not found

Set Access Level

PUT /admin/players/{account_id}/access-level

Change a player's in-game role. This controls game command access only and does not grant access to the admin panel.

Required Role: ADMIN

Only SUPERADMIN can assign admin or sysop roles, or change a SYSOP account's role.

Request Body:

{
  "role": "moderator"
}

Valid roles: guest, user, viewer, builder, moderator, admin, sysop. Assign panel access separately with PUT /admin/users/{account_id}.

Response (200):

{
  "account_id": "550e8400-...",
  "username": "player1",
  "previous_role": "user",
  "new_role": "moderator",
  "message": "Access level updated successfully"
}

Errors: - 400 - Invalid account ID format, or invalid role value - 403 - Cannot change this account's role (protected role) - 404 - Account not found

Send Message

POST /admin/players/{account_id}/message

Send a message to an online player.

Required Role: MODERATOR

Request Body:

{
  "message": "Welcome to MAID!",
  "message_type": "admin"
}

Field Type Default Description
message string Message text (1–1000 characters, required)
message_type string "admin" Type prefix: "admin", "system", "broadcast", or custom

Response (200):

{
  "account_id": "550e8400-...",
  "username": "player1",
  "message_delivered": true,
  "message": "Message sent"
}

Errors: - 400 - Invalid account ID format, or player is not online - 404 - Account not found


World Management

Note: World management routes are defined in maid-stdlib and mounted by content packs at runtime.

Manage rooms, exits, and areas.

List Rooms

GET /admin/world/rooms

List rooms with filtering and pagination.

Required Role: VIEWER

Query Parameters: | Parameter | Type | Description | |-----------|------|-------------| | page | int | Page number (1-indexed) | | page_size | int | Items per page (1-100) | | area_id | UUID | Filter by area | | search | string | Search by room name |

Response (200):

{
  "rooms": [
    {
      "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,
      "player_count": 2
    }
  ],
  "total": 500,
  "page": 1,
  "page_size": 50,
  "has_next": true,
  "has_previous": false
}

Get Room

GET /admin/world/rooms/{room_id}

Get detailed information about a room.

Required Role: VIEWER

Create Room

POST /admin/world/rooms

Create a new room.

Required Role: BUILDER

Request Body:

{
  "name": "Secret Chamber",
  "description": "A hidden chamber behind the waterfall.",
  "area_id": "660e8400-e29b-41d4-a716-446655440001",
  "metadata": {
    "hidden": true,
    "terrain": "cave"
  }
}

Response (201):

{
  "id": "880e8400-e29b-41d4-a716-446655440003",
  "name": "Secret Chamber",
  "message": "Room created successfully"
}

Update Room

PUT /admin/world/rooms/{room_id}

Update a room's properties.

Required Role: BUILDER

Request Body:

{
  "name": "Updated Room Name",
  "description": "New description",
  "metadata": {
    "terrain": "stone"
  }
}

Delete Room

DELETE /admin/world/rooms/{room_id}

Delete a room and all its exits.

Required Role: ADMIN

Query Parameters: | Parameter | Type | Description | |-----------|------|-------------| | force | bool | Force deletion even if room contains entities |

Create Exit

POST /admin/world/exits

Create an exit between two rooms.

Required Role: BUILDER

Request Body:

{
  "from_room_id": "550e8400-e29b-41d4-a716-446655440000",
  "to_room_id": "660e8400-e29b-41d4-a716-446655440001",
  "direction": "north",
  "bidirectional": true,
  "reverse_direction": "south",
  "is_locked": false
}

Response (201):

{
  "from_room_id": "550e8400-e29b-41d4-a716-446655440000",
  "to_room_id": "660e8400-e29b-41d4-a716-446655440001",
  "direction": "north",
  "reverse_created": true,
  "message": "Exit created successfully"
}

Delete Exit

DELETE /admin/world/exits/{room_id}/{direction}

Delete an exit from a room.

Required Role: BUILDER

Query Parameters: | Parameter | Type | Description | |-----------|------|-------------| | bidirectional | bool | Also delete the reverse exit |

Get World Graph

GET /admin/world/graph

Get the world topology as a graph for visualization.

Required Role: VIEWER

Query Parameters: | Parameter | Type | Description | |-----------|------|-------------| | area_id | UUID | Filter to a specific area |

Response (200):

{
  "nodes": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "label": "Town Square",
      "area_id": "660e8400-e29b-41d4-a716-446655440001",
      "area_name": "Town",
      "player_count": 2
    }
  ],
  "edges": [
    {
      "source": "550e8400-e29b-41d4-a716-446655440000",
      "target": "770e8400-e29b-41d4-a716-446655440002",
      "label": "north",
      "bidirectional": true
    }
  ],
  "total_rooms": 500,
  "total_exits": 1200,
  "total_areas": 12
}

Get Graph Neighbors

GET /admin/world/graph/neighbors/{room_id}

Get the immediate neighbors of a room in the world graph.

Required Role: VIEWER

Get Room Entities

GET /admin/world/rooms/{room_id}/entities

Get all entities located in a specific room.

Required Role: VIEWER

List Areas

GET /admin/world/areas

List all areas/zones in the world.

Required Role: VIEWER

Response (200):

{
  "areas": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "name": "Town",
      "description": "The main town area",
      "room_count": 50,
      "player_count": 15,
      "metadata": {}
    }
  ],
  "total": 12
}


Logs

View, search, and export server logs.

Search Logs

GET /admin/logs/

Search log entries with filters.

Required Role: VIEWER

Query Parameters: | Parameter | Type | Description | |-----------|------|-------------| | start_time | datetime | Start of time range | | end_time | datetime | End of time range | | level | string | Minimum log level (DEBUG, INFO, WARNING, ERROR, CRITICAL) | | logger_name | string | Logger name prefix | | message_pattern | string | Regex pattern for message | | limit | int | Maximum results (1-1000) | | offset | int | Pagination offset |

Response (200):

{
  "entries": [
    {
      "timestamp": "2024-01-15T10:30:00Z",
      "level": "INFO",
      "logger_name": "maid_engine.core.engine",
      "message": "Server started successfully",
      "module": "engine",
      "func_name": "start",
      "line_no": 123,
      "exc_info": null
    }
  ],
  "total": 1500,
  "has_more": true
}

Get Recent Logs

GET /admin/logs/recent

Get the most recent log entries.

Required Role: VIEWER

Query Parameters: | Parameter | Type | Description | |-----------|------|-------------| | count | int | Number of entries (1-500) |

List Loggers

GET /admin/logs/loggers

Get information about all known loggers.

Required Role: VIEWER

Response (200):

[
  {
    "name": "maid_engine.core.engine",
    "level": "INFO",
    "effective_level": "INFO",
    "handlers_count": 2
  }
]

Export Logs

POST /admin/logs/export

Export logs to JSON or CSV format.

Required Role: ADMIN

Request Body:

{
  "format": "json",
  "start_time": "2024-01-15T00:00:00Z",
  "end_time": "2024-01-15T23:59:59Z",
  "level": "WARNING",
  "max_entries": 10000
}

Response: Streaming file download

Get Log Statistics

GET /admin/logs/stats

Get statistics about logged messages.

Required Role: VIEWER

Response (200):

{
  "total_entries": 10000,
  "buffer_capacity": 10000,
  "by_level": {
    "DEBUG": 5000,
    "INFO": 3500,
    "WARNING": 1000,
    "ERROR": 450,
    "CRITICAL": 50
  },
  "top_loggers": {
    "maid_engine.core.engine": 2500,
    "maid_engine.network.telnet": 1500
  }
}


Configuration

View and modify server configuration.

Get All Configuration

GET /admin/config/

Get all configuration sections.

Required Role: VIEWER

Response (200):

[
  {
    "name": "game",
    "description": "Core game engine settings",
    "fields": {
      "tick_rate": 4.0,
      "max_players": 1000
    },
    "schema_info": {
      "tick_rate": {
        "type": "float",
        "description": "Game ticks per second",
        "is_secret": false,
        "required": false,
        "default": 4.0
      }
    }
  }
]

Get Configuration Section

GET /admin/config/{section}

Get a specific configuration section.

Required Role: VIEWER

Get Configuration Value

GET /admin/config/{section}/{key}

Get a specific configuration value.

Required Role: VIEWER

Response (200):

{
  "section": "game",
  "key": "tick_rate",
  "value": 4.0,
  "value_type": "float",
  "description": "Game ticks per second",
  "is_secret": false,
  "is_mutable": true
}

Validate Configuration Change

POST /admin/config/validate

Validate a proposed configuration change without applying it.

Required Role: ADMIN

Request Body:

{
  "section": "game",
  "key": "tick_rate",
  "value": 8.0
}

Response (200):

{
  "valid": true,
  "errors": [],
  "warnings": [],
  "current_value": 4.0
}

Update Configuration Value

PUT /admin/config/{section}/{key}

Update a configuration value at runtime.

Required Role: SUPERADMIN

Request Body:

{
  "value": 8.0
}

Reload Configuration

POST /admin/config/reload

Reload configuration from files and clear runtime overrides.

Required Role: SUPERADMIN

Get Runtime Overrides

GET /admin/config/overrides

Get all runtime configuration overrides.

Required Role: ADMIN

Clear Runtime Override

DELETE /admin/config/{section}/{key}/override

Clear a runtime override and revert to the base value.

Required Role: SUPERADMIN

Get Configuration Audit Log

GET /admin/config/audit

Get the audit log of configuration changes.

Required Role: ADMIN

Query Parameters: | Parameter | Type | Description | |-----------|------|-------------| | section | string | Filter by config section | | key | string | Filter by config key | | username | string | Filter by user who made the change | | action | string | Filter by action type | | offset | int | Pagination offset | | limit | int | Maximum results |


Content Packs

Manage content packs (plugins).

Note: Observability endpoints (/metrics, /healthz, /readyz, /livez) are served on a separate internal port (9090) and are not part of the admin API. See the REST API documentation for details.

List Content Packs

GET /admin/packs/

List all available and loaded content packs.

Required Role: VIEWER

Query Parameters: | Parameter | Type | Description | |-----------|------|-------------| | loaded_only | bool | Only show loaded packs |

Response (200):

{
  "packs": [
    {
      "name": "maid-stdlib",
      "display_name": "MAID Standard Library",
      "version": "0.1.0",
      "description": "Standard components and utilities",
      "is_loaded": true,
      "is_available": true
    }
  ],
  "total_available": 5,
  "total_loaded": 3
}

Get Pack Details

GET /admin/packs/{pack_name}

Get detailed information about a content pack.

Required Role: VIEWER

Response (200):

{
  "manifest": {
    "name": "maid-stdlib",
    "version": "0.1.0",
    "display_name": "MAID Standard Library",
    "description": "Standard components and utilities",
    "authors": ["MAID Team"],
    "license": "MIT",
    "homepage": "https://github.com/example/maid",
    "keywords": ["stdlib", "components"]
  },
  "status": {
    "is_loaded": true,
    "is_available": true,
    "can_unload": false,
    "blocking_dependents": ["maid-classic-rpg"],
    "load_order": 0
  },
  "dependencies": [],
  "capabilities": {
    "provides": ["stdlib"],
    "requires": []
  },
  "resources": {
    "systems": 5,
    "events": 12,
    "commands": 15
  }
}

Reload Content Pack

POST /admin/packs/{pack_name}/reload

Reload a content pack (unload and load again).

Required Role: ADMIN

Response (200):

{
  "success": true,
  "message": "Content pack 'maid-stdlib' reloaded successfully",
  "pack_name": "maid-stdlib",
  "reload_time_ms": 125.5
}

Get Pack Dependencies

GET /admin/packs/{pack_name}/dependencies

Get the dependency tree for a content pack.

Required Role: VIEWER

Query Parameters: | Parameter | Type | Description | |-----------|------|-------------| | include_transitive | bool | Include indirect dependencies |

Get Pack Dependents

GET /admin/packs/{pack_name}/dependents

Get packs that depend on this pack.

Required Role: VIEWER

Load Content Pack

POST /admin/packs/{pack_name}/load

Load a content pack, optionally auto-loading its dependencies.

Required Role: ADMIN

Request Body:

{
  "resolve_dependencies": true
}

Unload Content Pack

POST /admin/packs/{pack_name}/unload

Unload a content pack if no other loaded packs depend on it.

Required Role: ADMIN

Get Load Order

GET /admin/packs/load-order

Get the order in which packs were loaded.

Required Role: VIEWER


AI Pricing

Manage AI provider pricing configuration for cost tracking and budgeting.

Get AI Pricing

GET /admin/ai/pricing

Get pricing configuration for all AI providers and models.

Required Role: ADMIN

Response (200):

{
  "entries": {
    "anthropic/claude-sonnet-4-20250514": {
      "prompt_cost_per_1k": 0.003,
      "completion_cost_per_1k": 0.015,
      "effective_date": ""
    }
  },
  "default_prompt_cost": 0.0,
  "default_completion_cost": 0.0,
  "daily_token_budget": 100000,
  "per_player_daily_token_budget": 5000
}

Update AI Pricing

PUT /admin/ai/pricing

Update pricing configuration. Returns the updated PricingConfigModel.

Required Role: ADMIN

Request Body:

{
  "entries": {
    "anthropic/claude-sonnet-4-20250514": {
      "prompt_cost_per_1k": 0.003,
      "completion_cost_per_1k": 0.015,
      "effective_date": ""
    }
  },
  "default_prompt_cost": 0.001,
  "default_completion_cost": 0.002,
  "daily_token_budget": 100000,
  "per_player_daily_token_budget": 5000
}

Response (200):

{
  "entries": {
    "anthropic/claude-sonnet-4-20250514": {
      "prompt_cost_per_1k": 0.003,
      "completion_cost_per_1k": 0.015,
      "effective_date": ""
    }
  },
  "default_prompt_cost": 0.001,
  "default_completion_cost": 0.002,
  "daily_token_budget": 100000,
  "per_player_daily_token_budget": 5000
}


AI Players

Endpoints for listing, inspecting, and summarising AI Players managed by the engine's AIPlayerManager.

Lifecycle & takeover endpoints

The same /admin/ai-players/ router also exposes durable CRUD and control endpoints that require the ADMIN role: POST / (create, 201), PUT /{player_id} (update), DELETE /{player_id} (delete, 204), POST /{player_id}/pause, /resume, /takeover, /release, POST /{player_id}/control/commands (202), and GET /{player_id}/control/output. A takeover returns 409 Conflict while another admin holds control; SUPERADMIN can force control or release. See Managing AI Players for durable definitions, takeover semantics, and audit-logging details.

List AI Players

GET /admin/ai-players/

List all AI players with status and summary metrics.

Required Role: VIEWER

Response (200):

[
  {
    "player_id": "ai-player-001",
    "entity_id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "active",
    "personality": "friendly_merchant",
    "config_name": "shopkeeper-v1",
    "uptime_seconds": 3600.5,
    "total_actions": 142,
    "total_violations": 0,
    "total_cost": 0.032,
    "daily_tokens": 4500
  }
]

Returns an empty list when the AI player system is not enabled.

AI Player Summary

GET /admin/ai-players/summary

Aggregate stats across all AI players.

Required Role: VIEWER

Response (200):

{
  "total_players": 5,
  "active_players": 3,
  "total_actions": 1024,
  "total_violations": 2,
  "total_cost": 0.85,
  "violations_by_type": {
    "rate_limit": 1,
    "content_filter": 1
  },
  "costs_by_player": {
    "ai-player-001": 0.25,
    "ai-player-002": 0.60
  }
}

AI Player Metrics

GET /admin/ai-players/{player_id}/metrics

Detailed per-player metrics including recent bug reports.

Required Role: VIEWER

Response (200):

{
  "player_id": "ai-player-001",
  "total_cost": 0.25,
  "total_actions": 142,
  "total_violations": 0,
  "daily_tokens": 4500,
  "violations_by_type": {},
  "recent_bug_reports": [
    {
      "id": "report-uuid",
      "title": "NPC stuck in loop",
      "category": "behavior",
      "severity": "low",
      "status": "open"
    }
  ]
}

Error (404): AI player system not enabled, or player not found.

AI Player Violations

GET /admin/ai-players/{player_id}/violations

Violation history for a specific AI player.

Required Role: VIEWER

Response (200):

[
  {
    "timestamp": "2024-01-15T10:30:00Z",
    "anomaly_type": "content_filter",
    "severity": "medium",
    "command": "say offensive_content",
    "details": "Content filter triggered on NPC dialogue output"
  }
]

Error (404): AI player system not enabled, or player not found.


Balance / Economy

Balance analysis endpoints used by the admin Balance Dashboard. These provide combat curves, economy flow data, and content density metrics. When no live world state is available, combat and economy endpoints return default sample data. The content density endpoint returns live data when an engine is bound, or sample data when no engine is available.

Combat Balance

GET /admin/balance/combat

Combat balance curves across player levels (HP and DPS for players vs. monsters).

Required Role: VIEWER

Response (200):

{
  "levels": [1, 2, 3, 4, 5],
  "player_hp": [100.0, 125.0, 150.0, 175.0, 200.0],
  "monster_hp": [80.0, 110.0, 140.0, 170.0, 200.0],
  "player_dps": [10.0, 13.5, 17.0, 20.5, 24.0],
  "monster_dps": [8.0, 11.2, 14.4, 17.6, 20.8]
}

Economy Balance

GET /admin/balance/economy

Aggregated currency sources and sinks with net flow.

Required Role: VIEWER

Response (200):

{
  "sources": [
    {"name": "Quest Rewards", "amount": 15000},
    {"name": "Mob Drops", "amount": 8500},
    {"name": "Vendor Sales", "amount": 2300}
  ],
  "sinks": [
    {"name": "Vendor Purchases", "amount": 9000},
    {"name": "Repair Costs", "amount": 4200},
    {"name": "Training", "amount": 3100}
  ],
  "net_flow": 9500.0
}

Content Density

GET /admin/balance/content

Room, NPC, item, and quest counts grouped by zone. Returns live world data when the engine is available, or sample data otherwise.

Required Role: VIEWER

Response (200):

{
  "zones": [
    {"name": "Tutorial Village", "rooms": 12, "npcs": 8, "items": 15, "quests": 5},
    {"name": "Dark Forest", "rooms": 24, "npcs": 18, "items": 22, "quests": 7},
    {"name": "Goblin Camp", "rooms": 10, "npcs": 14, "items": 10, "quests": 3}
  ]
}


NPC Memory, Relationships & Knowledge

Note: These routes are defined in maid-stdlib and mounted by content packs at runtime.

Get Memory Stats

GET /admin/memory/stats

Get NPC memory system statistics.

Required Role: VIEWER

Get NPC Memories

GET /admin/memory/{npc_id}

Get all memories for an NPC.

Required Role: VIEWER

Get NPC-Player Memories

GET /admin/memory/{npc_id}/{player_id}

Get NPC memories for a specific player interaction.

Required Role: VIEWER

Delete Memory

DELETE /admin/memory/{memory_id}

Delete a specific memory entry.

Required Role: ADMIN

Get NPC Relationships

GET /admin/relationships/{npc_id}

Get all relationships for an NPC.

Required Role: VIEWER

Get NPC-Player Relationship

GET /admin/relationships/{npc_id}/{player_id}

Get detailed relationship data between an NPC and a player.

Required Role: VIEWER

Update NPC-Player Relationship

PUT /admin/relationships/{npc_id}/{player_id}

Update relationship dimensions (trust, respect, friendliness) between an NPC and a player.

Required Role: BUILDER

Get NPC Knowledge

GET /admin/knowledge/{npc_id}

Get the knowledge graph for an NPC.

Required Role: VIEWER


Editor (Visual Map Editor)

REST endpoints for the visual map editor backend. Provides exclusive entity locking (so multiple builders don't overwrite each other), per-user layout persistence, and editor extension discovery.

Locks are in-memory, TTL-based (default 5 minutes), and automatically expire. They are broadcast over the EDITOR WebSocket channel on acquire/release.

List Active Locks

GET /admin/editor/locks

List all currently active (unexpired) entity locks.

Required Role: VIEWER

Response (200):

{
  "locks": [
    {
      "entity_id": "550e8400-e29b-41d4-a716-446655440000",
      "user_id": "660e8400-e29b-41d4-a716-446655440001",
      "username": "builder1",
      "acquired_at": "2024-01-15T10:30:00Z",
      "expires_at": "2024-01-15T10:35:00Z"
    }
  ],
  "total": 1
}

Acquire Entity Lock

POST /admin/editor/locks/{entity_id}

Acquire an exclusive lock on an entity. If the caller already holds the lock, it is renewed.

Required Role: BUILDER

Path Parameters: - entity_id — UUID of the entity to lock

Response (200):

{
  "success": true,
  "entity_id": "550e8400-e29b-41d4-a716-446655440000",
  "message": "Lock acquired",
  "lock": {
    "entity_id": "550e8400-e29b-41d4-a716-446655440000",
    "user_id": "660e8400-e29b-41d4-a716-446655440001",
    "username": "builder1",
    "acquired_at": "2024-01-15T10:30:00Z",
    "expires_at": "2024-01-15T10:35:00Z"
  }
}

Error (400): Invalid entity_id (must be a UUID).

Error (409): Entity is already locked by another user.

Release Entity Lock

DELETE /admin/editor/locks/{entity_id}

Release an entity lock. The lock owner can release their own lock. Users with ADMIN or SUPERADMIN role can force-release locks held by other users.

Required Role: BUILDER

Path Parameters: - entity_id — UUID of the entity to unlock

Response (200):

{
  "success": true,
  "entity_id": "550e8400-e29b-41d4-a716-446655440000",
  "message": "Lock released"
}

Error (400): Invalid entity_id.

Error (403): Lock is held by another user and the caller is not ADMIN/SUPERADMIN.

Error (404): No active lock exists for this entity.

Renew Entity Lock

PUT /admin/editor/locks/{entity_id}

Extend the TTL of a lock owned by the caller.

Required Role: BUILDER

Path Parameters: - entity_id — UUID of the entity whose lock to renew

Response (200):

{
  "success": true,
  "entity_id": "550e8400-e29b-41d4-a716-446655440000",
  "message": "Lock renewed",
  "lock": {
    "entity_id": "550e8400-e29b-41d4-a716-446655440000",
    "user_id": "660e8400-e29b-41d4-a716-446655440001",
    "username": "builder1",
    "acquired_at": "2024-01-15T10:30:00Z",
    "expires_at": "2024-01-15T10:40:00Z"
  }
}

Error (400): Invalid entity_id.

Error (403): Lock is held by another user.

Error (404): No active lock exists for this entity.

Load Editor Layout

GET /admin/editor/layout/{user_id}

Retrieve the saved editor layout (node positions and viewport) for a user. Users can read their own layout; ADMIN/SUPERADMIN can read anyone's.

Required Role: VIEWER

Response (200):

{
  "user_id": "660e8400-e29b-41d4-a716-446655440001",
  "node_positions": {
    "room-uuid-1": {"x": 100.0, "y": 200.0},
    "room-uuid-2": {"x": 300.0, "y": 150.0}
  },
  "viewport": {"x": 0.0, "y": 0.0, "zoom": 1.0},
  "updated_at": "2024-01-15T10:30:00Z"
}

Returns empty node_positions and viewport if no layout has been saved.

Error (403): Attempting to read another user's layout without ADMIN/SUPERADMIN role.

Save Editor Layout

PUT /admin/editor/layout/{user_id}

Save or update the editor layout for a user. Users can save their own layout; ADMIN/SUPERADMIN can save anyone's.

Required Role: VIEWER

Request Body:

{
  "node_positions": {
    "room-uuid-1": {"x": 100.0, "y": 200.0},
    "room-uuid-2": {"x": 300.0, "y": 150.0}
  },
  "viewport": {"x": 50.0, "y": 25.0, "zoom": 1.5}
}

The request body also accepts camelCase (nodePositions) for frontend compatibility.

Response (200):

{
  "user_id": "660e8400-e29b-41d4-a716-446655440001",
  "node_positions": {
    "room-uuid-1": {"x": 100.0, "y": 200.0},
    "room-uuid-2": {"x": 300.0, "y": 150.0}
  },
  "viewport": {"x": 50.0, "y": 25.0, "zoom": 1.5},
  "updated_at": "2024-01-15T10:35:00Z"
}

Error (403): Attempting to modify another user's layout without ADMIN/SUPERADMIN role.

List Editor Extensions

GET /admin/editor/extensions

Return editor extensions (custom inspector tabs, toolbar buttons) registered by content packs. Returns an empty list when no extensions are installed.

Required Role: VIEWER

Response (200):

{
  "extensions": [
    {
      "pack_name": "classic-rpg",
      "tabs": [
        {
          "id": "combat-stats",
          "label": "Combat Stats",
          "target_types": ["npc", "monster"]
        }
      ],
      "toolbar_items": [
        {
          "id": "spawn-npc",
          "label": "Spawn NPC",
          "icon": "user-plus",
          "action": "spawn_npc_dialog"
        }
      ]
    }
  ]
}


Maintenance

Get Maintenance Status

GET /admin/maintenance/status

Return the current maintenance window state for the admin panel banner. Always returns a payload regardless of whether a window is active.

Produced by build_admin_status_payload() in maid_engine.maintenance.render.

Required Role: VIEWER

Response (200):

All five fields (active, reason, eta, started_at, status_url) are always present.

Field Type Description
active bool Whether a maintenance window is currently open.
reason str Human-readable MOTD. Defaults to "The server is undergoing maintenance. Please reconnect later." when no custom message is configured. Always a string (never null).
eta str | null ISO-8601 expected-end timestamp, or null if unset.
started_at str | null ISO-8601 timestamp when the window began (populated by the persistence overlay). null while maintenance is inactive.
status_url str | null Operator-configured status page URL, or null if unset.

When no maintenance window is active:

{
  "active": false,
  "reason": "The server is undergoing maintenance. Please reconnect later.",
  "eta": null,
  "started_at": null,
  "status_url": null
}

When a maintenance window is active:

{
  "active": true,
  "reason": "Scheduled database migration in progress.",
  "eta": "2024-01-15T11:00:00Z",
  "started_at": "2024-01-15T10:00:00Z",
  "status_url": "https://status.example.com"
}

WebSocket Endpoints

Main Admin WebSocket

WS /admin/ws

Main WebSocket connection for subscribing to various channels.

Required Role: VIEWER

Authentication: Authenticated via HTTP-only maid_access_token cookie (set at login), or by sending a JSON message as the first frame:

{
  "type": "auth",
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Origin Validation: All WebSocket connections require a valid Origin header matching one of the server's configured admin.allowed_origins. Connections without an Origin header or with an unrecognised origin are rejected.

Subscribe to a channel:

{
  "type": "subscribe",
  "channel": "metrics"
}

Both singular "channel" (a string) and plural "channels" (an array of strings) are accepted for subscribe and unsubscribe messages:

{
  "type": "subscribe",
  "channels": ["metrics", "entities", "logs"]
}

List WebSocket Clients

GET /admin/ws/clients

List currently connected admin WebSocket clients.

Required Role: ADMIN

Dashboard WebSocket

WS /admin/dashboard/ws

Real-time dashboard metrics streaming. The server broadcasts the full DashboardData JSON object directly (not wrapped in a {type, data} envelope):

Required Role: VIEWER

Receive metrics:

{
  "timestamp": "2024-01-15T10:30:00Z",
  "server": { "uptime_seconds": 3600.5, "uptime_formatted": "1h 0m 0s", "..." : "..." },
  "players": { "online_count": 15, "..." : "..." },
  "world": { "entity_count": 5000, "..." : "..." },
  "ai": { "total_cost": 0.0, "total_tokens": 0 },
  "persistence": { "dirty_count": 0 },
  "content_packs": ["maid-stdlib", "maid-classic-rpg"],
  "recent_events": []
}

Log Stream WebSocket

WS /admin/logs/stream

Real-time log streaming via WebSocket.

Required Role: MODERATOR

Authentication confirmation (sent after successful auth):

{
  "type": "auth_success",
  "username": "admin"
}

Receive log entries:

{
  "type": "log_entry",
  "data": {
    "timestamp": "2024-01-15T10:30:00Z",
    "level": "INFO",
    "logger_name": "maid_engine.core.engine",
    "message": "Player connected: player1"
  }
}

Send ping:

{
  "type": "ping"
}

Receive pong:

{
  "type": "pong"
}


Error Responses

All endpoints return standard HTTP error codes with JSON error bodies:

{
  "detail": "Error message describing what went wrong"
}
Code Description
400 Bad Request - Invalid input
401 Unauthorized - Invalid or missing token
403 Forbidden - Insufficient permissions
404 Not Found - Resource does not exist
409 Conflict - Resource already exists
500 Internal Server Error
503 Service Unavailable - Engine not ready

Rate Limiting

The Admin API may implement rate limiting. When rate limited, you will receive:

  • Status code: 429 Too Many Requests
  • Headers:
  • X-RateLimit-Limit: Maximum requests per window
  • X-RateLimit-Remaining: Requests remaining
  • X-RateLimit-Reset: Timestamp when limit resets