Skip to content

Latest commit

 

History

History
753 lines (493 loc) · 14.5 KB

File metadata and controls

753 lines (493 loc) · 14.5 KB

Maryl API reference

Base URL: your panel origin (e.g. https://panel.example.com).

All /api/* routes except POST /api/auth/login require authentication via:

  • Session cookiepanel_session, set by login; or
  • API keyAuthorization: Bearer maryl_<token>

Unauthenticated requests receive 401 with { "error": "Unauthorized" }.

Common responses

Status Meaning
400 Invalid input — { "error": "message" }
401 Not authenticated
403 Authenticated but not permitted
404 Resource not found — { "error": "message" }
202 Accepted — async job started
201 Created
429 Login rate limited — { "error": "Too many login attempts…" } with Retry-After header

JSON request bodies use Content-Type: application/json unless noted.


Authorization

Three layers apply, in order:

  1. Authentication — valid session or API key
  2. Global roleADMIN, OPERATOR, or VIEWER
  3. Server permission — for routes under /api/servers/{slug}/…

Global role checks

Helper Allows
Any authenticated user VIEWER, OPERATOR, ADMIN
Write OPERATOR, ADMIN
Admin ADMIN only

Server permissions

Used by requireServerPermission(slug, permission):

Permission Typical routes
read Server detail, metrics, backup list, schedules list
power Lifecycle, schedule create/update/delete/run
console Logs, log stream, RCON
files.read File list/read, config GET, SFTP info
files.write File write/upload, config PUT
backup Create backup, download archive
admin Members, backup restore/delete, migrate
delete Delete server

Global shortcuts (no ServerMember row needed):

  • ADMIN → all permissions on all servers
  • OPERATOR → all except delete and admin
  • VIEWERread only; needs membership for anything else

Job control permissions

POST /api/jobs/{id}/run and POST /api/jobs/{id}/cancel require write access and permission derived from the job:

Job type Required permission
PROVISION, RESTORE, MIGRATE admin on job's server
START, STOP, RESTART, UPDATE power
BACKUP backup
DELETE delete
FILE_OPERATION files.write
DATABASE_BACKUP, DATABASE_RESTORE global ADMIN (no server)

Auth

POST /api/auth/login

Public. Rate limited (20 attempts / 10 min per IP, 8 per email).

Body:

{ "email": "admin@panel.local", "password": "admin" }

Response 200:

{ "ok": true }

Sets panel_session cookie.

POST /api/auth/logout

Destroys the current session.

Response 200:

{ "ok": true }

Dashboard & fleet

GET /api/dashboard

Auth: any user.

Returns fleet summary — server counts by status, node capacity, recent alerts, batch CPU/RAM metrics.

GET /api/servers/list

Auth: any user.

Compact server list for navigation and dropdowns.

GET /api/activity

Auth: any user.

Query: ?limit=50 (optional).

Audit log entries (user actions across the panel).

GET /api/nodes

Auth: any user.

All registered nodes with capacity usage and server counts.

POST /api/nodes

Auth: admin.

Body:

{
  "name": "node-1",
  "hostname": "10.0.0.5",
  "dockerHost": "unix:///var/run/docker.sock",
  "dockerAuthToken": null,
  "cpuTotal": 16,
  "memoryTotalMb": 65536,
  "diskTotalGb": 500
}

name must match ^[a-z0-9-]+$.

GET /api/nodes/{name}

Auth: any user. Docker auth token is not returned (only hasDockerAuthToken: true/false).

PATCH /api/nodes/{name}

Auth: admin.

Body (all optional):

{
  "hostname": "10.0.0.5",
  "dockerHost": "unix:///var/run/docker.sock",
  "dockerAuthToken": "",
  "status": "ONLINE",
  "drainMode": false,
  "maintenanceMode": false,
  "cpuTotal": 16,
  "memoryTotalMb": 65536,
  "diskTotalGb": 500,
  "portRanges": [{ "start": 27015, "end": 27100, "protocol": "udp" }]
}

POST /api/nodes/{name}/heartbeat

Auth: admin. Manually triggers Docker ping, capacity sync, and disk usage scan.

GET /api/templates

Auth: any user.

POST /api/templates

Auth: admin. Create a custom game template.

GET /api/templates/{slug}

Auth: any user.

PATCH /api/templates/{slug}

Auth: admin.

GET /api/allocations

Auth: any user.

Query: ?node={name} for per-node port detail.

GET /api/alerts

Auth: any user.

GET /api/alerts/{id}

Auth: any user. Supports ?status= filter (lists alerts, not a single-resource fetch).

PATCH /api/alerts/{id}

Auth: write.

Body:

{ "action": "acknowledge" }

or { "action": "resolve" }.


Servers

POST /api/servers

Auth: admin. Creates server record and enqueues PROVISION job.

Body:

{
  "name": "my-cs2-server",
  "templateSlug": "cs2",
  "nodeName": "node-1",
  "cpuLimit": 4,
  "memoryLimitMb": 8192,
  "diskLimitGb": 40,
  "ports": [
    { "name": "game", "protocol": "udp", "internalPort": 27015, "externalPort": 27015 },
    { "name": "query", "protocol": "udp", "internalPort": 27016, "externalPort": 27016 },
    { "name": "rcon", "protocol": "tcp", "internalPort": 27020, "externalPort": 27020 }
  ],
  "envVars": [
    { "key": "LGSM_GAMESERVER", "value": "cs2server" }
  ]
}

Response 201: { "server": {…}, "job": {…} }

GET /api/servers/{slug}

Auth: server read.

Server detail with live Docker stats when available.

DELETE /api/servers/{slug}

Auth: server delete. Enqueues DELETE job.

POST /api/servers/{slug}/lifecycle

Auth: server power.

Body:

{ "action": "start" }

action: start | stop | restart.

Response 202: { "job": {…} }

POST /api/servers/{slug}/migrate

Auth: server admin.

Body:

{ "targetNodeId": "clxyz…" }

GET /api/servers/{slug}/metrics

Auth: server read.

Historical CPU and memory samples.


Console & RCON

GET /api/servers/{slug}/console

Auth: server console.

Query: ?tail=300, ?since=<unix_ms>.

Snapshot of container logs (JSON).

GET /api/servers/{slug}/console/stream

Auth: server console.

Server-Sent Events stream of live container logs.

POST /api/servers/{slug}/console/session

Auth: server console + global write (OPERATOR+).

Body:

{ "mode": "shell" }

Response:

{
  "sessionId": "<64 hex>",
  "mode": "shell",
  "wsPath": "/api/console/ws",
  "expiresAt": "2026-01-01T00:00:00.000Z"
}

Connect WebSocket to ws://{host}:{CONSOLE_WS_PORT}/api/console/ws?session={sessionId} with the panel_session cookie present. Session is single-use.

POST /api/servers/{slug}/rcon

Auth: server console. Server must be RUNNING.

Body:

{ "command": "status" }

Response:

{
  "command": "status",
  "output": "",
  "timestamp": "2026-01-01T00:00:00.000Z"
}

Files & config

GET /api/servers/{slug}/files

Auth: server files.read.

Query Behavior
?path= List directory (default root)
?path=…&mode=file Read text file content

PUT /api/servers/{slug}/files

Auth: server files.write.

Body:

{ "path": "serverfiles/csgo/cfg/server.cfg", "content": "" }

POST /api/servers/{slug}/files

Auth: server files.write.

Body (one of):

{ "action": "mkdir", "path": "newdir" }
{ "action": "delete", "path": "oldfile.txt" }
{ "action": "rename", "path": "old", "newPath": "new" }

POST /api/servers/{slug}/files/upload

Auth: server files.write. multipart/form-data with file and path fields. Max size: FILE_UPLOAD_MAX_BYTES.

GET /api/servers/{slug}/config

Auth: server files.read.

Structured game config view — field definitions from the template profile with current values. Secret fields return ******** instead of the real value.

PUT /api/servers/{slug}/config

Auth: server files.write.

Body:

{
  "fields": {
    "hostname": "My Server",
    "rcon_password": "********"
  }
}

Send ******** to keep an existing secret unchanged.

GET /api/servers/{slug}/sftp

Auth: server files.read.

Returns SSH/SFTP connection hints (host, port, username, data path). Does not provision credentials — uses your host's SSH.


Backups

GET /api/servers/{slug}/backups

Auth: server read.

POST /api/servers/{slug}/backups

Auth: server backup. Creates tarball asynchronously.

Response 202: { "backup": {…} }

GET /api/servers/{slug}/backups/{id}

Auth: server read. Does not include storagePath.

DELETE /api/servers/{slug}/backups/{id}

Auth: server admin.

GET /api/servers/{slug}/backups/{id}/download

Auth: server backup. Returns the archive file stream.

POST /api/servers/{slug}/backups/{id}/restore

Auth: server admin.

Body:

{ "confirm": true }

Stops the server, optionally creates a safety backup, extracts the archive, restarts.


Schedules

GET /api/servers/{slug}/schedules

Auth: server read.

POST /api/servers/{slug}/schedules

Auth: server power. Creator must hold every permission required by the schedule's tasks.

Body:

{
  "label": "Nightly backup",
  "cron": "0 4 * * *",
  "timezone": "UTC",
  "enabled": true,
  "tasks": [
    { "action": "BACKUP" },
    { "action": "RESTART" }
  ]
}

Task actions: BACKUP, START, STOP, RESTART, CONSOLE_COMMAND.

CONSOLE_COMMAND is accepted by the API but not executed by the schedule runner.

PATCH /api/servers/{slug}/schedules/{id}

Auth: server power.

Body:

{ "enabled": false, "label": "Updated label" }

DELETE /api/servers/{slug}/schedules/{id}

Auth: server power.

POST /api/servers/{slug}/schedules/{id}

Auth: server power. Run all tasks immediately.


Server members (Access tab)

GET /api/servers/{slug}/members

Auth: server admin.

POST /api/servers/{slug}/members

Auth: server admin.

Body:

{
  "userId": "clxyz…",
  "permissions": {
    "power": true,
    "console": true,
    "files.read": true,
    "files.write": false,
    "backup": true,
    "delete": false,
    "admin": false
  }
}

Omit permissions to use defaults based on the user's global role.

PATCH /api/servers/{slug}/members/{id}

Auth: server admin. Update permissions object.

DELETE /api/servers/{slug}/members/{id}

Auth: server admin.


Provision wizard helpers

GET /api/provision/options

Auth: any user. Templates and nodes with placement/capacity checks.

POST /api/provision/validate-ports

Auth: any user.

Body:

{
  "nodeId": "clxyz…",
  "ports": [
    { "externalPort": 27015, "protocol": "udp" }
  ]
}

Response:

{ "valid": true, "conflicts": [] }

PUT /api/provision/validate-ports

Auth: any user. Suggest an available port.

Body:

{ "nodeId": "clxyz…", "protocol": "udp", "preferred": 27015 }

Jobs

GET /api/jobs

Auth: any user.

Query: ?status=PENDING, ?server={slug}.

POST /api/jobs/{id}/run

Auth: write + job permission (see table above). Retries failed or stuck jobs.

POST /api/jobs/{id}/cancel

Auth: write + job permission.


Users & API keys

GET /api/users

Auth: admin.

POST /api/users

Auth: admin.

Body:

{
  "email": "ops@example.com",
  "name": "Operator",
  "password": "minimum-8-chars",
  "role": "OPERATOR"
}

role: ADMIN | OPERATOR | VIEWER (default OPERATOR).

PATCH /api/users/{id}

Auth: admin.

Body (all optional):

{ "name": "", "role": "OPERATOR", "password": "new-password" }

Password change revokes all sessions and API keys for that user.

DELETE /api/users/{id}

Auth: admin. Cannot delete yourself or the last admin.

GET /api/api-keys

Auth: admin.

POST /api/api-keys

Auth: admin.

Body:

{ "name": "terraform" }

Response 201:

{
  "key": {
    "id": "",
    "name": "terraform",
    "token": "maryl_a1b2c3…",
    "createdAt": ""
  }
}

token is shown once.

DELETE /api/api-keys/{id}

Auth: admin. Revokes the key.


Panel database

GET /api/database/backups

Auth: admin.

POST /api/database/backups

Auth: admin. Enqueues DATABASE_BACKUP job (pg_dump).

GET /api/database/backups/{id}

Auth: admin. Download dump file.

POST /api/database/backups/{id}/restore

Auth: admin. Enqueues DATABASE_RESTORE job. Destructive — replaces the panel database.

GET /api/database/recovery

Auth: admin. Current recovery/safety-backup state.


Maryl runtime config

GET /api/maryl/branding

Auth: any authenticated user.

Query: ?width=120 (terminal width for ASCII art wrapping).

GET /api/maryl/config

Auth: admin. Parsed maryl.yaml as JSON.

PATCH /api/maryl/config

Auth: admin. Partial branding update.

PUT /api/maryl/config

Auth: admin. Reset to defaults.

GET /api/maryl/config/raw

Auth: admin. Raw YAML text.

PUT /api/maryl/config/raw

Auth: admin.

Body:

{ "raw": "branding:\n  enabled: true\n" }

Invalid YAML returns 400.


Example: provision a server with curl

# Login (save cookie)
curl -c cookies.txt -X POST https://panel.example.com/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"admin@panel.local","password":"admin"}'

# Or use an API key
export MARYL_TOKEN="maryl_…"

# Create server
curl -b cookies.txt -X POST https://panel.example.com/api/servers \
  -H "Content-Type: application/json" \
  -d '{
    "name": "arena-1",
    "templateSlug": "cs2",
    "nodeName": "node-1",
    "cpuLimit": 4,
    "memoryLimitMb": 8192,
    "diskLimitGb": 40,
    "ports": [
      {"name":"game","protocol":"udp","internalPort":27015,"externalPort":27015},
      {"name":"query","protocol":"udp","internalPort":27016,"externalPort":27016},
      {"name":"rcon","protocol":"tcp","internalPort":27020,"externalPort":27020}
    ],
    "envVars": [{"key":"LGSM_GAMESERVER","value":"cs2server"}]
  }'

# Poll job status
curl -b cookies.txt "https://panel.example.com/api/jobs?server=arena-1"