python
# ============================================================
# CRAFTY CONTROLLER CONNECTION SETTINGS
# Edit these three values to match your Crafty instance
# ============================================================
CRAFTY_HOST = "https://your-server-ip-or-domain" # e.g. "https://192.168.1.50" or "https://crafty.myserver.com"
CRAFTY_PORT = 8443 # Default Crafty port — change if you use a custom one
CRAFTY_API_KEY = "your_api_key_here" # Found in Crafty UI → Profile → API Key
# Derived base URL — do not edit
CRAFTY_BASE_URL = f"{CRAFTY_HOST}:{CRAFTY_PORT}/api/v2"
# Standard headers used on every request
HEADERS = {
"Authorization": f"Bearer {CRAFTY_API_KEY}",
"Content-Type": "application/json"
}
# ============================================================
Where to find your API key: Log into Crafty Controller → click your username (top right) → API Key tab → copy the token.
Crafty Controller exposes a REST API under /api/v2. All endpoints require Bearer token authentication. The API covers:
DomainWhat you can doServersList, create, start, stop, restart, kill, deleteConsoleSend commands, stream logsStatsCPU, RAM, player counts, ping, world sizeBackupsList, trigger, restore, deleteSchedulesList, create, update, delete cron tasksFilesBrowse, read, write, upload, delete server filesUsersList, create, update, delete, assign rolesRolesList, create, assign permissionsCrafty ConfigServer-wide settings, announcements
Install dependencies first:
bash
pip install requests urllib3
Base client to reuse everywhere:
python
import requests
import urllib3
import json
# Suppress SSL warnings if using self-signed cert (common on local Crafty installs)
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def crafty_get(path, params=None):
"""GET request to Crafty API."""
url = f"{CRAFTY_BASE_URL}{path}"
resp = requests.get(url, headers=HEADERS, params=params, verify=False)
resp.raise_for_status()
return resp.json()
def crafty_post(path, payload=None):
"""POST request to Crafty API."""
url = f"{CRAFTY_BASE_URL}{path}"
resp = requests.post(url, headers=HEADERS, json=payload or {}, verify=False)
resp.raise_for_status()
return resp.json()
def crafty_patch(path, payload=None):
"""PATCH request to Crafty API."""
url = f"{CRAFTY_BASE_URL}{path}"
resp = requests.patch(url, headers=HEADERS, json=payload or {}, verify=False)
resp.raise_for_status()
return resp.json()
def crafty_delete(path):
"""DELETE request to Crafty API."""
url = f"{CRAFTY_BASE_URL}{path}"
resp = requests.delete(url, headers=HEADERS, verify=False)
resp.raise_for_status()
return resp.json()
python
def list_servers():
"""Returns all servers registered in Crafty."""
data = crafty_get("/servers")
for s in data.get("data", []):
print(f"[{s['server_id']}] {s['server_name']} — Running: {s['running']}")
return data
python
def get_server(server_id: str):
"""Get full details for a specific server."""
return crafty_get(f"/servers/{server_id}")
python
def create_server(
name: str,
java_path: str = "java",
jar_path: str = "server.jar",
min_ram: int = 512,
max_ram: int = 2048,
port: int = 25565,
crash_detection: bool = True,
auto_start: bool = False,
):
payload = {
"name": name,
"java_path": java_path,
"jar_path": jar_path,
"min_ram": min_ram,
"max_ram": max_ram,
"port": port,
"crash_detection": crash_detection,
"auto_start": auto_start,
}
return crafty_post("/servers", payload)
python
def update_server(server_id: str, changes: dict):
"""
Patch any combination of server fields.
Example changes: {"max_ram": 4096, "auto_start": True}
"""
return crafty_patch(f"/servers/{server_id}", changes)
python
def delete_server(server_id: str):
"""Permanently delete a server from Crafty (does NOT delete files by default)."""
return crafty_delete(f"/servers/{server_id}")
All power actions POST to /servers/{server_id}/action/{action}.
python
def server_action(server_id: str, action: str):
"""
Valid actions:
start_server — start the server
stop_server — graceful stop
restart_server — stop then start
kill_server — force-kill (SIGKILL)
backup_server — trigger a backup immediately
"""
return crafty_post(f"/servers/{server_id}/action/{action}")
# Convenience wrappers
def start_server(server_id): return server_action(server_id, "start_server")
def stop_server(server_id): return server_action(server_id, "stop_server")
def restart_server(server_id): return server_action(server_id, "restart_server")
def kill_server(server_id): return server_action(server_id, "kill_server")
python
def send_command(server_id: str, command: str):
"""
Send any console command to a running server.
Examples: "say Hello!", "op PlayerName", "weather clear", "stop"
"""
return crafty_post(f"/servers/{server_id}/action/send_command", {"command": command})
python
def op_player(server_id, player): return send_command(server_id, f"op {player}")
def deop_player(server_id, player): return send_command(server_id, f"deop {player}")
def kick_player(server_id, player, reason=""):
cmd = f"kick {player}" + (f" {reason}" if reason else "")
return send_command(server_id, cmd)
def ban_player(server_id, player, reason=""):
cmd = f"ban {player}" + (f" {reason}" if reason else "")
return send_command(server_id, cmd)
def say_message(server_id, message): return send_command(server_id, f"say {message}")
def set_time(server_id, time): return send_command(server_id, f"time set {time}")
def set_weather(server_id, weather): return send_command(server_id, f"weather {weather}")
def give_item(server_id, player, item, amount=1):
return send_command(server_id, f"give {player} {item} {amount}")
def teleport(server_id, player, x, y, z):
return send_command(server_id, f"tp {player} {x} {y} {z}")
python
def get_logs(server_id: str):
"""Retrieve recent console log output for a server."""
return crafty_get(f"/servers/{server_id}/logs")
python
def get_server_stats(server_id: str):
"""
Returns live stats:
- running (bool)
- online_players (int)
- max_players (int)
- cpu (float, %)
- mem (float, MB)
- world_size (str)
- server_port (int)
- version (str)
- desc (str, MOTD)
"""
return crafty_get(f"/servers/{server_id}/stats")
def print_server_stats(server_id: str):
stats = get_server_stats(server_id)
d = stats.get("data", {})
status = "🟢 Online" if d.get("running") else "🔴 Offline"
print(f"""
Status : {status}
Players : {d.get('online_players', 0)}/{d.get('max_players', 0)}
CPU : {d.get('cpu', 0):.1f}%
RAM : {d.get('mem', 0):.1f} MB
Version : {d.get('version', 'N/A')}
World : {d.get('world_size', 'N/A')}
""")
python
def get_all_stats():
"""Snapshot stats for every registered server."""
return crafty_get("/servers/stats")
python
def list_backups(server_id: str):
return crafty_get(f"/servers/{server_id}/backups")
python
def trigger_backup(server_id: str):
"""Immediately run a backup for the given server."""
return server_action(server_id, "backup_server")
python
def get_backup_config(server_id: str):
return crafty_get(f"/servers/{server_id}/backups/config")
python
def update_backup_config(server_id: str, changes: dict):
"""
Example changes:
{
"backup_path": "/backups/myserver",
"max_backups": 5,
"compress": True,
"shutdown_server": False
}
"""
return crafty_patch(f"/servers/{server_id}/backups/config", changes)
python
def delete_backup(server_id: str, backup_filename: str):
return crafty_delete(f"/servers/{server_id}/backups/{backup_filename}")
python
def list_schedules(server_id: str):
return crafty_get(f"/servers/{server_id}/schedules")
python
def create_schedule(
server_id: str,
name: str,
action: str, # "command", "start", "stop", "restart", "backup"
interval: int, # how often (in the unit below)
interval_type: str, # "hours", "minutes", "days", "weeks" or a cron string
command: str = "", # only needed for action="command"
enabled: bool = True,
one_time: bool = False,
cron_string: str = "",
):
payload = {
"name": name,
"action": action,
"interval": interval,
"interval_type": interval_type,
"command": command,
"enabled": enabled,
"one_time": one_time,
"cron_string": cron_string,
}
return crafty_post(f"/servers/{server_id}/schedules", payload)
# Example: restart every 6 hours
# create_schedule(server_id, "6hr restart", "restart", 6, "hours")
# Example: daily backup
# create_schedule(server_id, "Daily Backup", "backup", 24, "hours")
# Example: broadcast message every 30 minutes
# create_schedule(server_id, "30min broadcast", "command", 30, "minutes", command="say Server has been running for 30 more minutes!")
python
def update_schedule(server_id: str, schedule_id: str, changes: dict):
return crafty_patch(f"/servers/{server_id}/schedules/{schedule_id}", changes)
python
def delete_schedule(server_id: str, schedule_id: str):
return crafty_delete(f"/servers/{server_id}/schedules/{schedule_id}")
python
def list_files(server_id: str, path: str = ""):
"""
Browse the server's file system.
path is relative to the server's root directory.
Leave empty to list the root.
"""
return crafty_get(f"/servers/{server_id}/files", params={"path": path})
python
def read_file(server_id: str, file_path: str):
"""
Read the contents of a text file (e.g. server.properties, ops.json).
file_path is relative to server root.
"""
return crafty_get(f"/servers/{server_id}/files", params={"path": file_path, "file": "true"})
python
def write_file(server_id: str, file_path: str, content: str):
"""
Overwrite a file with new content.
Useful for editing server.properties, whitelist.json, etc.
"""
return crafty_post(f"/servers/{server_id}/files/save", {
"path": file_path,
"content": content
})
python
def create_directory(server_id: str, dir_path: str):
return crafty_post(f"/servers/{server_id}/files/create", {"path": dir_path})
python
def delete_file(server_id: str, file_path: str):
return crafty_post(f"/servers/{server_id}/files/delete", {"path": file_path})
python
def upload_file(server_id: str, local_file_path: str, upload_dir: str = ""):
"""Upload a local file into the server's directory."""
url = f"{CRAFTY_BASE_URL}/servers/{server_id}/files/upload"
with open(local_file_path, "rb") as f:
files = {"file": f}
data = {"path": upload_dir}
# Note: don't pass Content-Type header for multipart
auth_headers = {"Authorization": f"Bearer {CRAFTY_API_KEY}"}
resp = requests.post(url, headers=auth_headers, files=files, data=data, verify=False)
resp.raise_for_status()
return resp.json()
python
def list_users():
return crafty_get("/users")
python
def get_user(user_id: str):
return crafty_get(f"/users/{user_id}")
python
def create_user(
username: str,
password: str,
email: str = "",
enabled: bool = True,
superuser: bool = False,
):
return crafty_post("/users", {
"username": username,
"password": password,
"email": email,
"enabled": enabled,
"superuser": superuser,
})
python
def update_user(user_id: str, changes: dict):
"""
Example changes:
{"password": "newpass", "enabled": False}
"""
return crafty_patch(f"/users/{user_id}", changes)
python
def delete_user(user_id: str):
return crafty_delete(f"/users/{user_id}")
python
def set_user_server_permissions(user_id: str, server_id: str, permissions: list):
"""
Permissions list can contain any of:
"Commands", "Terminal", "Logs", "Schedule", "Backup",
"Files", "Config", "Players"
"""
return crafty_patch(f"/users/{user_id}", {
"server_permissions": {server_id: permissions}
})
python
def list_roles():
return crafty_get("/roles")
python
def get_role(role_id: str):
return crafty_get(f"/roles/{role_id}")
python
def create_role(name: str, servers: dict = None):
"""
servers = {
"server_id_1": {"permissions": "1111111"}, # bitmask string
...
}
"""
return crafty_post("/roles", {
"role_name": name,
"servers": servers or {}
})
python
def update_role(role_id: str, changes: dict):
return crafty_patch(f"/roles/{role_id}", changes)
python
def delete_role(role_id: str):
return crafty_delete(f"/roles/{role_id}")
python
def assign_role_to_user(user_id: str, role_id: str):
return crafty_patch(f"/users/{user_id}", {"roles": [role_id]})
python
def get_crafty_info():
"""Returns Crafty version, uptime, OS info, Python version, etc."""
return crafty_get("/server/stats")
python
def get_audit_log():
return crafty_get("/crafty/audit-log")
All functions can raise requests.HTTPError. Wrap calls for production use:
python
def safe_call(fn, *args, **kwargs):
try:
result = fn(*args, **kwargs)
if result.get("status") == "ok":
return result.get("data")
else:
print(f"Crafty API error: {result.get('error')} — {result.get('data')}")
return None
except requests.exceptions.ConnectionError:
print(f"❌ Cannot connect to Crafty at {CRAFTY_BASE_URL}. Check HOST and PORT.")
except requests.exceptions.HTTPError as e:
print(f"❌ HTTP {e.response.status_code}: {e.response.text}")
except Exception as e:
print(f"❌ Unexpected error: {e}")
return None
python
def dashboard():
servers = crafty_get("/servers").get("data", [])
print(f"{'Name':<25} {'Status':<10} {'Players':<12} {'CPU':<8} {'RAM'}")
print("-" * 70)
for s in servers:
sid = s["server_id"]
stats = get_server_stats(sid).get("data", {})
status = "Online " if stats.get("running") else "Offline"
players = f"{stats.get('online_players',0)}/{stats.get('max_players',0)}"
cpu = f"{stats.get('cpu', 0):.1f}%"
ram = f"{stats.get('mem', 0):.0f}MB"
print(f"{s['server_name']:<25} {status:<10} {players:<12} {cpu:<8} {ram}")
python
def restart_all():
servers = crafty_get("/servers").get("data", [])
for s in servers:
if s.get("running"):
print(f"Restarting {s['server_name']}...")
restart_server(s["server_id"])
python
def set_property(server_id: str, key: str, value: str):
"""
Read server.properties, change one key, write it back.
Example: set_property(sid, "max-players", "50")
"""
result = read_file(server_id, "server.properties")
content = result.get("data", {}).get("content", "")
lines = content.splitlines()
new_lines = []
found = False
for line in lines:
if line.startswith(f"{key}="):
new_lines.append(f"{key}={value}")
found = True
else:
new_lines.append(line)
if not found:
new_lines.append(f"{key}={value}")
write_file(server_id, "server.properties", "\n".join(new_lines))
print(f"✅ Set {key}={value}")
Every Crafty API response follows this envelope:
json
{
"status": "ok", // "ok" or "error"
"data": { ... }, // payload (dict, list, or string)
"error": null // error message if status is "error"
}
Always check response["status"] == "ok" before using response["data"].
verify=False. In production, provide your real cert path: verify="/path/to/cert.pem"./api/v2 routes). Crafty 3 uses a different API.