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
- User Management
- Server Stats
- Dashboard
- Entity Management
- Player Management
- World Management
- Logs
- Configuration
- Content Packs
- AI Pricing
- AI Players
- Balance / Economy
- NPC Memory, Relationships & Knowledge
- Editor (Visual Map Editor)
- WebSocket Endpoints
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:
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-onlymaid_csrf_tokencookie. Thesecureflag 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):
Response (200):
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):
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:
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):
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):
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):
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:
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:
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):
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):
Errors:
- 404 - Entity not found, or entity does not have tag
Player Management¶
Note: Player management routes are defined in
maid-stdliband 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
statusorrolefilter 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
ADMINorSYSOPaccounts unless the acting admin isSUPERADMIN.
Request Body:
| 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):
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:
| 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
SUPERADMINcan assignadminorsysoproles, or change aSYSOPaccount's role.
Request Body:
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:
| 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-stdliband 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:
Response (200):
Update Configuration Value¶
PUT /admin/config/{section}/{key}
Update a configuration value at runtime.
Required Role: SUPERADMIN
Request Body:
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:
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-stdliband 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:
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:
Both singular "channel" (a string) and plural "channels" (an array of strings) are accepted for subscribe and unsubscribe messages:
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):
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:
Receive pong:
Error Responses¶
All endpoints return standard HTTP error codes with JSON error bodies:
| 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 windowX-RateLimit-Remaining: Requests remainingX-RateLimit-Reset: Timestamp when limit resets