mcpd exposes one REST surface, used by two kinds of caller.
- The Plesk extension authenticates with the master token from
/etc/plesk-minecraft/config.jsonand addsX-Mcpd-ActorandX-Mcpd-Client-Idheaders so the daemon can scope and audit the request. The browser never sees this token: it calls the panel, andApiControllerproxies. - External clients authenticate with a token created in Settings → REST API tokens. These are Exaroton-style: scoped, optionally pinned to one server, and stored only as a SHA-256 hash.
Authorization: Bearer <token>
X-Api-Token: <token> is accepted as an alternative. Everything except
GET /api/v1/health requires a token.
Scopes are cumulative — each one implies the ones before it.
| Scope | Grants |
|---|---|
read |
list servers, read status, players, metrics, catalogues |
console |
plus read the console and send commands |
control |
plus start, stop, restart, kill, edit settings |
files |
plus the file manager, backups and content installs |
admin |
plus create and delete servers, tokens and daemon settings |
A token with a server_id may only touch that server. A token with a non-zero
client_id sees only the servers owned by, or shared with, that Plesk client.
Errors are JSON with an error key and a meaningful status: 400 malformed input,
401 bad token, 403 scope or ownership, 404 unknown route or server, 409 the
server is in the wrong state (for example editing a port while running), 502 an
upstream catalogue failed.
Two ways to follow a console, because they suit different callers.
Polling — what the panel uses, since Plesk's nginx buffers streamed responses:
GET /api/v1/servers/{id}/console?since=<cursor>&wait=1
Returns {"lines": [{"seq", "ts", "line"}], "cursor": N, "state": "..."}. Pass the
returned cursor as the next since. since=-1 returns the tail. A cursor older
than the retained window is fast-forwarded rather than returning nothing, so a client
that fell behind resumes cleanly. wait=1 long-polls for up to 20 seconds.
WebSocket — for external clients:
GET /api/v1/servers/{id}/console/ws (Upgrade: websocket)
The server sends {"type":"hello"|"console"|"state"|"players", ...} frames. Send
{"type":"command","command":"say hi"} to run a command (needs the console scope).
| Method | Path | Scope |
|---|---|---|
GET |
/api/v1/info |
read |
GET |
/api/v1/settings |
admin |
PUT |
/api/v1/settings |
admin |
GET |
/api/v1/audit |
admin |
| Method | Path | Scope |
|---|---|---|
GET |
/api/v1/loaders |
read |
GET |
/api/v1/loaders/{loader}/versions |
read |
GET |
/api/v1/loaders/{loader}/versions/{version}/builds |
read |
GET |
/api/v1/java |
read |
POST |
/api/v1/java/{major} |
admin |
DELETE |
/api/v1/java/{major} |
admin |
GET |
/api/v1/properties/schema |
read |
GET |
/api/v1/content/sources |
read |
GET |
/api/v1/content/search |
read |
GET |
/api/v1/content/{source}/{project} |
read |
GET |
/api/v1/content/{source}/{project}/versions |
read |
| Method | Path | Scope |
|---|---|---|
GET |
/api/v1/tokens |
admin |
POST |
/api/v1/tokens |
admin |
DELETE |
/api/v1/tokens/{token_id} |
admin |
| Method | Path | Scope |
|---|---|---|
GET |
/api/v1/servers |
read |
POST |
/api/v1/servers |
admin |
| Method | Path | Scope |
|---|---|---|
GET |
/api/v1/servers/{sid} |
read |
PATCH |
/api/v1/servers/{sid} |
admin |
DELETE |
/api/v1/servers/{sid} |
admin |
POST |
/api/v1/servers/{sid}/start |
control |
POST |
/api/v1/servers/{sid}/stop |
control |
POST |
/api/v1/servers/{sid}/restart |
control |
POST |
/api/v1/servers/{sid}/kill |
control |
POST |
/api/v1/servers/{sid}/command |
console |
POST |
/api/v1/servers/{sid}/install |
admin |
POST |
/api/v1/servers/{sid}/eula |
control |
GET |
/api/v1/servers/{sid}/console |
console |
GET |
/api/v1/servers/{sid}/console/ws |
console |
GET |
/api/v1/servers/{sid}/status |
read |
GET |
/api/v1/servers/{sid}/metrics |
read |
GET |
/api/v1/servers/{sid}/properties |
read |
PUT |
/api/v1/servers/{sid}/properties |
control |
GET |
/api/v1/servers/{sid}/access |
admin |
PUT |
/api/v1/servers/{sid}/access |
admin |
GET |
/api/v1/servers/{sid}/notifications |
admin |
PUT |
/api/v1/servers/{sid}/notifications |
admin |
POST |
/api/v1/servers/{sid}/notifications/test |
admin |
GET |
/api/v1/servers/{sid}/players |
read |
POST |
/api/v1/servers/{sid}/players/{action} |
control |
GET |
/api/v1/servers/{sid}/worlds |
read |
POST |
/api/v1/servers/{sid}/worlds/activate |
control |
POST |
/api/v1/servers/{sid}/worlds/reset |
control |
POST |
/api/v1/servers/{sid}/worlds/import |
files |
GET |
/api/v1/servers/{sid}/worlds/export |
files |
DELETE |
/api/v1/servers/{sid}/worlds/{world} |
admin |
GET |
/api/v1/servers/{sid}/files |
files |
GET |
/api/v1/servers/{sid}/files/read |
files |
GET |
/api/v1/servers/{sid}/files/download |
files |
POST |
/api/v1/servers/{sid}/files/write |
files |
POST |
/api/v1/servers/{sid}/files/upload |
files |
POST |
/api/v1/servers/{sid}/files/mkdir |
files |
POST |
/api/v1/servers/{sid}/files/rename |
files |
POST |
/api/v1/servers/{sid}/files/move |
files |
POST |
/api/v1/servers/{sid}/files/copy |
files |
POST |
/api/v1/servers/{sid}/files/compress |
files |
POST |
/api/v1/servers/{sid}/files/extract |
files |
POST |
/api/v1/servers/{sid}/files/chmod |
files |
DELETE |
/api/v1/servers/{sid}/files |
files |
GET |
/api/v1/servers/{sid}/backups |
read |
POST |
/api/v1/servers/{sid}/backups |
files |
GET |
/api/v1/servers/{sid}/backups/{bid}/download |
files |
POST |
/api/v1/servers/{sid}/backups/{bid}/restore |
admin |
DELETE |
/api/v1/servers/{sid}/backups/{bid} |
files |
GET |
/api/v1/servers/{sid}/schedules |
read |
POST |
/api/v1/servers/{sid}/schedules |
admin |
PATCH |
/api/v1/servers/{sid}/schedules/{schid} |
admin |
DELETE |
/api/v1/servers/{sid}/schedules/{schid} |
admin |
GET |
/api/v1/servers/{sid}/content |
read |
POST |
/api/v1/servers/{sid}/content |
files |
GET |
/api/v1/servers/{sid}/content/updates |
read |
DELETE |
/api/v1/servers/{sid}/content/{cid} |
files |
75 endpoints total.
Create a server and follow the install:
TOKEN=... # from Settings -> REST API tokens
API=https://your-host:8720/api/v1
curl -s -X POST "$API/servers" \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"name":"Survival","loader":"paper","mc_version":"1.21.8",
"memory_mb":4096,"sleep_enabled":true,"eula_accepted":true}'
curl -s "$API/servers/<id>/console?since=-1" -H "Authorization: Bearer $TOKEN"Start it and wait for it to come up:
curl -s -X POST "$API/servers/<id>/start" -H "Authorization: Bearer $TOKEN"
until [ "$(curl -s "$API/servers/<id>" -H "Authorization: Bearer $TOKEN" \
| python3 -c 'import json,sys;print(json.load(sys.stdin)["state"])')" = running ]; do
sleep 5
doneInstall a plugin from Modrinth:
curl -s -X POST "$API/servers/<id>/content" \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"source":"modrinth","project":"chunky","kind":"plugin"}'Upload a file (raw body, path in the query string):
curl -s -X POST "$API/servers/<id>/files/upload?path=plugins/config.yml" \
-H "Authorization: Bearer $TOKEN" --data-binary @config.ymlThe daemon binds 127.0.0.1 only by default. To let external clients in, turn on
Expose the REST API on a public port in the extension settings, choose a port, open
it in the firewall, and restart plesk-minecraftd. Put it behind TLS — the daemon
speaks plain HTTP and is designed to sit behind the panel or a reverse proxy.