Introduction
MangoPanel is a lightweight, self-hosted, hPanel-style shared hosting control panel. It provides a complete web hosting control plane designed for real account stacks, queued jobs, DNS management, SSL certificates, email services, databases, file managers, and backups.
MangoPanel uses a zero-external-framework Python HTTP API backed by SQLite WAL mode. Desired infrastructure state is written to SQLite, while a dedicated local Node Agent processes privileged operations asynchronously.
Key Platform Highlights
- Account Isolation: One dedicated Docker Compose stack per customer account (OpenLiteSpeed, MariaDB, phpMyAdmin, Filebrowser, SFTP, Mail).
- Audited Control Plane: Single SQLite database (`mangopanel.sqlite3`) tracking users, websites, databases, mailboxes, and job logs.
- Built-in Security: Signed JWT session tokens, mandatory TOTP multi-factor authentication, and short-lived launch tokens for sub-services.
- Dual-Port Panel Architecture: Separate listening ports for Client (`8000`) and Admin (`8001`) to isolate administrative API routes.
Requirements
Before installing MangoPanel, ensure your server or development environment satisfies the following system specifications:
| Component | Minimum Requirement | Recommended |
|---|---|---|
| Operating System | Linux (Ubuntu 22.04 / 24.04 LTS, Debian 12) or macOS | Ubuntu 24.04 LTS Server |
| Python | Python 3.10+ | Python 3.12+ |
| Container Engine | Docker Engine 24.0+ & Docker Compose v2 | Docker Engine 26.0+ |
| System Tools | `git`, `make`, `curl`, `tar`, `sqlite3` | `git`, `make`, `curl`, `rsync` |
| Network Ports | `8000` (Client), `8001` (Admin) | Public IPs for 80, 443, 53 (DNS), 587 (SMTP), 993 (IMAP) |
Installation
Choose one of the following methods to install MangoPanel on your host system.
Option A: Automated One-Line Install (Recommended)
Fresh clone and auto-installation using `git`:
git clone https://github.com/servermango/MangoPanel.git MangoPanel && cd MangoPanel && bash scripts/install.sh
Option B: Archive Install (If Git is unavailable)
mkdir MangoPanel && curl -fsSL https://codeload.github.com/servermango/MangoPanel/tar.gz/refs/heads/main | tar -xz -C MangoPanel --strip-components=1 && cd MangoPanel && bash scripts/install.sh
Option C: Manual Makefile Step-by-Step
cd MangoPanel
# 1. Install system prerequisites, Python venv, Docker tools & pre-pull images
make install
# 2. Check Python, Docker, and listening ports
make dev-init
# 3. Start the client and admin panel services (seeds dev data on first run)
make dev-up
Quick Start
Once installation finishes, MangoPanel runs as a local background daemon or development server.
| Interface | URL | Default Credentials |
|---|---|---|
| Client Portal | `http://127.0.0.1:8000/` | `[email protected]` / `ChangeMe-DevOnly-123!` |
| Admin Panel | `http://127.0.0.1:8001/admin` | `[email protected]` / `ChangeMe-DevOnly-123!` |
| Client Signup | `http://127.0.0.1:8000/signup` | Self-service client registration |
| System Status | `http://127.0.0.1:8000/status` | Public platform feature state page |
In development mode (`MP_DEV_AUTH_TEST_MODE=true`), the universal TOTP verification code is 000000.
Basic Setup
Follow these steps to configure your initial administrative credentials, define hosting plans, and provision your first customer accounts.
1. Admin Setup & Recovery
When starting MangoPanel on a fresh database with zero admins, visiting `http://
- Fill in the admin email address and password.
- The system generates an initial TOTP authenticator secret and displays an `otpauth://` QR code.
- Scan the QR code in Google Authenticator / 1Password and verify a code before completing setup.
- Once an admin exists, public first-admin setup is permanently locked.
CLI Admin Recovery Tool
If an administrator loses password or 2FA access, run `scripts/admin_recovery.py` directly on the server host:
# Reset admin password
sudo python3 scripts/admin_recovery.py --email [email protected] --set-password
# Generate new TOTP secret & display QR setup URI
sudo python3 scripts/admin_recovery.py --email [email protected] --enable-totp
# Disable TOTP requirement for emergency login
sudo python3 scripts/admin_recovery.py --email [email protected] --disable-totp
2. Creating Hosting Plans
Before customer accounts can be provisioned, define at least one active Hosting Plan in the Admin Panel under Plans (`/admin#plans`).
| Plan Field | Description | Example Value |
|---|---|---|
| Name & Slug | Human readable label and identifier | `Starter Plan` (`starter`) |
| CPU & RAM Limit | Per-account container execution limits | `1.0 CPU` / `512 MB RAM` |
| Storage & Inodes | Disk quota in MB and file count limit | `10000 MB` / `250000 inodes` |
| Website Count | Maximum allowed hosted domains | `5 websites` |
| Databases & Mailboxes | Max MySQL/PostgreSQL DBs and email accounts | `5 DBs` / `10 mailboxes` |
| DNS Policy | DNS provider selection (Local PowerDNS / Cloudflare) | `Cloudflare Managed` |
3. Provisioning Customer Clients
Customer accounts can be created through two primary workflows:
Workflow A: Self-Service Signup Page (`/signup`)
- User visits `http://
:8000/signup`. - Enters email, password, and selects an available hosting plan.
- API executes `POST /api/public/signup`, generates customer credentials, sets up 2FA, and assigns an account ID (e.g., `u000001`).
Workflow B: Admin Dashboard Provisioning
- Admin opens Admin Panel > Clients.
- Clicks Create Client Account.
- Enters user email, password, and selects the hosting plan.
- The system creates the user, generates account directory structure under `user_files/accounts/
/`, and queues container deployment.
Cloudflare as DNS - Setup Instructions
MangoPanel supports Cloudflare as an authoritative DNS provider for managed domains. Configure Cloudflare credentials in the Admin panel under DNS Settings.
Cloudflare API Prerequisites
- Account ID: Found in the right sidebar of the Cloudflare Dashboard on any domain's Overview page.
- API Token: Generated under My Profile > API Tokens in Cloudflare.
The Cloudflare API Token must include these explicit permissions:
• Zone > Zone > Edit (or Zone:Read for zone verification)
• Zone > DNS > Edit (for managing DNS records)
• Zone > Page Rules > Edit (Required: allows MangoPanel to automatically create ACME HTTP-01 challenge bypass rules for SSL Auto-Issuance on Cloudflare-proxied domains)
Automated ACME Challenge Page Rules
When a website is added or connected to MangoPanel using Cloudflare DNS, MangoPanel automatically creates a Cloudflare Page Rule targeting http://*example.com/.well-known/acme-challenge/* with action always_use_https: off. This prevents Cloudflare from redirecting Let's Encrypt HTTP ACME challenges to HTTPS port 443 before Caddy has issued an SSL certificate, eliminating HTTP 525 (SSL Handshake Failed) deadlocks.
Syncing Existing Websites
To add missing Cloudflare Page Rules to existing websites, you can use any of the following methods:
- API Endpoint (Client Area):
POST /api/client/dns/sync-cloudflare-rules - API Endpoint (Admin Area):
POST /api/admin/dns/sync-cloudflare-rules - CLI Script: Run
python scripts/sync_cloudflare_rules.pyon your server
Domain & Subdomain DNS Setup
To instruct users to set up a server domain or subdomain for MangoPanel and tool access, create DNS A-records pointing to your server's IP address.
For example, if you want your users to reach the panel from hosting.servermango.com, create the following DNS A-records pointing to your server's public IP address:
| Type | Name / Host | Target / Value | Purpose |
|---|---|---|---|
A |
hosting.servermango.com (or @) |
<SERVER_IP> |
Main panel and web traffic |
A |
*.files.hosting.servermango.com (or *.files) |
<SERVER_IP> |
Per-account Filebrowser tool subdomain routing |
Once configured, access MangoPanel at:
- Admin Panel:
http://hosting.servermango.com:8001/admin - Client Panel:
http://hosting.servermango.com:8000/client(orhttp://hosting.servermango.com:8000/)
Current Build Features
- Python standard-library HTTP API with zero framework overhead.
- SQLite WAL-mode schema with versioned development seed data.
- Public client signup (`/signup`), client portal (`/`), admin panel (`/admin`), and status page (`/status`).
- Full coverage for websites, MySQL, PostgreSQL, mailboxes, SFTP, cron jobs, backups, Git deployments, and audit logs.
- Node-agent job runner with simulated and Docker Compose execution modes.
- Account-stack generator for OpenLiteSpeed, Filebrowser, phpMyAdmin, MariaDB, cron, SFTP, and shared mail scaffolding.
Service Command
MangoPanel includes a portable service wrapper at scripts/service that manages background panel processes and agent workers without requiring `systemd`:
# Service lifecycle management
bash scripts/service mangopanel start
bash scripts/service mangopanel status
bash scripts/service mangopanel restart
bash scripts/service mangopanel stop
# Makefile wrapper equivalent
make service ACTION=start
make service ACTION=status
On a production server, run the systemd service so the panel uses Docker mode and asynchronous agent jobs. This does not delete website content:
cd /root/MangoPanel
sudo systemctl start mangopanel.service
sudo systemctl status mangopanel.service --no-pager
# The service must define:
# MP_ENV=production
# MP_AGENT_MODE=docker
Do not start a second manual python -m mangopanel.app process on ports 8000/8001.
How It Works
Request flow in one line:
Browser → Panel API (writes SQLite + enqueues job) → Local Agent (provisions files & containers) → Status reported back to SQLite → UI displays result
Data Layout
All persistent control-plane state and customer hosting data lives under user_files/:
user_files/
accounts/ # per-customer hosting files (MP_ACCOUNT_ROOT)
u000001/
account.json
domains/<domain>/public_html/
databases/ mail/ backups/ ssl/ git/ .runtime/
data/ # control-plane database (MP_DATA_DIR)
mangopanel.sqlite3
Server Migration
Because all database state and customer files live inside `user_files/`, migration to a new server takes 5 simple steps:
- Stop Services:
bash scripts/service mangopanel stopon old server. - Install on New Server: Run installation script on destination machine.
- Transfer `user_files/`:
rsync -avzP /path/to/old/user_files/ root@new-server:/path/to/new/user_files/ - Start Services & Provision Stacks:
bash scripts/service mangopanel start& run agent stack rebuild. - Update DNS: Point A-records to the new server IP.
Local & Docker Development
# Start lightweight dev server (Simulated Mode)
make dev-up
# Start full Docker hosting stack (OpenLiteSpeed, MariaDB, SFTP containers)
make dev-up-docker
# Run unit tests
make test
# Run API smoke test suite
make dev-smoke
Thesis Integration Direction
MangoPanel is evaluated as a case study for Tariq Abdullah's Cloud Definition Language (CDL) M.Tech thesis research, exploring how shared-hosting platform intent can be compiled into explainable infrastructure artifacts.
Implementation Roadmap
- Phase 1 (Implemented): Shared interfaces and control-plane schema.
- Phase 2 (Implemented): Local PowerDNS adapter, ACME provider, and mail edge scaffolding.
- Phase 3 (Implemented): Hardening, auth validation, and client dashboard metrics integration.
Client Area API Reference & Parameters
MangoPanel provides a clean REST API for managing websites, DNS records, databases, mailboxes, cron jobs, files, SSL, and monitoring resource usage using API keys or Bearer JWT tokens.
1. API Key Guide — Client Panel
How to Generate a Client API Key
Client API keys are scoped to a hosting account, not just a user. There are two ways to create one:
- In the UI: Log in to the Client Area (port
8000), go to Account Settings > API Tokens, click Generate Token, and enter a descriptive name. - Via API (authenticated session or existing token):
Response:
POST /api/client/api-tokens Content-Type: application/json Authorization: Bearer mp_EXISTING_TOKEN {"name": "My Automation Key"}{"id": 3, "name": "My Automation Key", "token": "mp_a1b2c3d4e5f6..."}
Client API tokens will be rejected at authentication time with 403 api_access_disabled_for_plan unless the hosting account's plan has API Access enabled (allow_api_access = true). An admin must enable this flag on the plan. The API also blocks token creation at POST /api/client/api-tokens if the plan flag is off.
Token Format
Client tokens are prefixed with mp_ (without admin or reseller in the name), e.g. mp_a1b2c3.... The token is derived from secrets.token_hex(32) and SHA-256 hashed before storage — only shown once on creation.
Token Lifetime
Client tokens have no expiry by default (the expires_at field is NULL unless set). Use DELETE /api/client/api-tokens/{id} to revoke them.
Authentication Methods
# Option A: Bearer Header (Recommended)
Authorization: Bearer mp_YOUR_API_KEY
# Option B: X-API-Key Header
X-API-Key: mp_YOUR_API_KEY
# Option C: Query Parameter
GET /api/client/websites?api_key=mp_YOUR_API_KEY
# Optional: Scope to a specific hosting account (multi-account users)
X-Hosting-Account-ID: 2
Managing Client API Tokens via API
| Method & Endpoint | Description |
|---|---|
GET /api/client/api-tokens | List all tokens for the current hosting account |
POST /api/client/api-tokens | Generate a new token — requires {"name": "label"} body |
DELETE /api/client/api-tokens/{id} | Revoke a token by ID |
2. Code Snippets
cURL Example
# Create a new website
curl -X POST "http://your-server:8000/api/client/websites" \
-H "Authorization: Bearer mp_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"domain": "mysite.com", "php_version": "8.3"}'
Python Example
import requests
API_KEY = "mp_YOUR_API_KEY"
BASE_URL = "http://your-server:8000"
headers = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
# Add a new DNS A-record
payload = {
"domain_id": 1,
"name": "sub",
"type": "A",
"value": "127.0.0.1",
"ttl": 300,
"proxied": True
}
res = requests.post(f"{BASE_URL}/api/client/dns-records", headers=headers, json=payload)
print(res.json())
3. Complete Client Endpoint & Parameter Reference
| Method & Endpoint | Description | Parameters & Payload Fields |
|---|---|---|
GET /api/client/home |
Account overview, quotas, and resource summary | None (Optional query: account_id) |
GET /api/client/resource-usage |
CPU, Memory, Disk, and Inode trend metrics | Query: range (30m | 24h | 7d | 30d) |
POST /api/client/recalculate-usage |
Trigger immediate disk and inode usage recalculation | None |
GET /api/client/analytics |
Web access and bandwidth analytics | Query: website_id (int), range (24h | 7d | 30d) |
GET /api/client/php-info |
Fetch PHP configuration for a website | Query: website_id (required, int) |
GET /api/client/sync-jobs |
List background stack sync jobs for account | None |
GET /api/client/websites |
List all websites on account | None |
POST /api/client/websites |
Create a new website |
domain (string, required)php_version (string: "8.1", "8.2", "8.3")document_root (string, optional)index_enabled (bool, default true)modsec_enabled (bool, default true)analytics_enabled (bool, default true)
|
PATCH /api/client/websites/{id} |
Update website settings |
php_version (string)index_enabled (bool)modsec_enabled (bool)analytics_enabled (bool)
|
DELETE /api/client/websites/{id} |
Delete website and document root | None |
POST /api/client/websites/{id}/connection-check |
Test website OLS & PHP-FPM connectivity | None |
GET /api/client/php-versions |
List supported server PHP versions | None |
GET /api/client/domains |
List account domains | None |
POST /api/client/dns/check-domain |
Check domain DNS availability & Cloudflare zone status | domain (string, required) |
POST /api/client/dns/sync-cloudflare-rules |
Sync and ensure Cloudflare ACME challenge page rules for websites | website_id (int, optional) |
POST /api/client/domains/{id}/nameservers |
Update domain registrar nameservers | nameservers (array of strings, required) |
POST /api/client/domains/{id}/dns/rebuild |
Rebuild and publish authoritative DNS zone | None |
POST /api/client/domains/{id}/dns/verify-nameservers |
Verify nameservers resolution via public DNS | None |
GET /api/client/domains/{id}/dns/export |
Export DNS zone file in BIND format | None |
POST /api/client/domains/{id}/dns/migrate-provider |
Migrate DNS provider (Local vs Cloudflare) |
provider_key (string: "local-dev-dns", "cloudflare")cloudflare_account_id (int, required if Cloudflare)
|
GET /api/client/dns-records |
List DNS records | Query: domain_id (optional int filter) |
POST /api/client/dns-records |
Add a DNS record |
domain_id (int, required)name (string, required e.g., "sub" or "@")type (string: "A", "AAAA", "CNAME", "MX", "TXT", "NS", "SRV")value / content (string, required)ttl (int, default 300)priority (int, optional for MX/SRV)proxied (bool, optional for Cloudflare)
|
PATCH /api/client/dns-records/{id} |
Update existing DNS record | name, type, value, ttl, priority, proxied |
DELETE /api/client/dns-records/{id} |
Delete a DNS record | None |
GET /api/client/databases |
List MySQL / MariaDB databases | None |
POST /api/client/databases |
Create a MySQL database | name (string, required) |
DELETE /api/client/databases/{id} |
Delete a MySQL database | None |
GET /api/client/database-users |
List MySQL database users | None |
POST /api/client/database-users |
Create a MySQL user |
username (string, required)password (string, minimum 8 characters)
|
PATCH /api/client/database-users/{id} |
Change MySQL user password | password (string, required) |
DELETE /api/client/database-users/{id} |
Delete a MySQL user | None |
POST /api/client/database-grants |
Grant MySQL user database privileges |
database_id (int, required)user_id (int, required)privileges (string, default "ALL")
|
DELETE /api/client/database-grants/{id} |
Revoke database user grant | None |
GET /api/client/pg-databases |
List PostgreSQL databases | None |
POST /api/client/pg-databases |
Create a PostgreSQL database | name (string, required) |
DELETE /api/client/pg-databases/{id} |
Delete a PostgreSQL database | None |
GET /api/client/mailboxes |
List email mailboxes | None |
POST /api/client/mailboxes |
Create an email mailbox |
email (string, required e.g., "[email protected]")password (string, minimum 8 characters)quota_mb (int, default 1024)
|
PATCH /api/client/mailboxes/{id} |
Update mailbox password/quota |
password (string, optional)quota_mb (int, optional)
|
DELETE /api/client/mailboxes/{id} |
Delete an email mailbox | None |
GET /api/client/mail-forwarders |
List email forwarders | None |
POST /api/client/mail-forwarders |
Create an email forwarder |
source_email (string, required)destination_email (string, required)
|
DELETE /api/client/mail-forwarders/{id} |
Delete an email forwarder | None |
GET /api/client/ssh |
Get SSH access status & connection details | None |
POST /api/client/ssh/toggle |
Enable or disable SSH shell access | enabled (bool, required) |
POST /api/client/ssh/password |
Change account SSH / SFTP password | password (string, minimum 8 characters) |
GET /api/client/ftp-accounts |
List custom FTP accounts | None |
POST /api/client/ftp-accounts |
Create a custom FTP account |
username (string, required)password (string, minimum 8 characters)path (string, relative directory path)
|
DELETE /api/client/ftp-accounts/{id} |
Delete an FTP account | None |
GET /api/client/files/launch |
Generate single sign-on URL for File Manager | Query: path (optional directory path) |
GET /api/client/cron-jobs |
List scheduled cron jobs | None |
POST /api/client/cron-jobs |
Create a scheduled cron job |
schedule (string, standard 5-part cron syntax e.g. "0 * * * *")command (string, required e.g. "php /home/public_html/cron.php")
|
DELETE /api/client/cron-jobs/{id} |
Delete a cron job | None |
POST /api/client/ssl/issue |
Issue Let's Encrypt SSL certificate for a website | website_id (int, required) |
POST /api/client/ssl/custom |
Install custom SSL certificate |
website_id (int, required)certificate (PEM string, required)private_key (PEM string, required)
|
GET /api/client/backups |
List website backup archives | None |
POST /api/client/backups |
Create a full website backup archive | website_id (int, required) |
POST /api/client/hotlink-protection |
Configure image hotlink protection |
website_id (int, required)enabled (bool, required)allowed_domains (array of strings)
|
POST /api/client/ip-blocking |
Configure IP blocking rules |
website_id (int, required)blocked_ips (array of IP strings)
|
POST /api/client/directory-privacy |
Password protect a website directory (.htpasswd) |
website_id (int, required)path (string, relative directory path)username (string, required)password (string, required)
|
GET /api/client/api-tokens |
List generated Client API keys | None |
POST /api/client/api-tokens |
Generate a new Client API key | name (string, required descriptive label) |
DELETE /api/client/api-tokens/{id} |
Revoke/delete a Client API key | None |
Admin API
The Admin API is available on port 8001 and is accessible only to authenticated admin accounts. All endpoints are prefixed with /api/admin/. Authenticate using a long-lived API token generated from the Admin panel.
API Key Guide — Admin Panel
How to Generate an Admin API Key
Admin API keys are scoped to the individual admin account that created them. There are two ways to create one:
- In the UI: Log in to the Admin Panel (port
8001), go to Settings > API Tokens, click Generate Token, enter a name and optional permission scopes. - Via API (requires an active admin session or existing
admins.managetoken):Response (token is shown only once):POST /api/admin/api-tokens Content-Type: application/json Authorization: Bearer mp_admin_EXISTING_TOKEN {"name": "CI/CD Pipeline Key", "permissions": ["clients.manage", "hosting.manage"]}{"id": 5, "name": "CI/CD Pipeline Key", "token": "mp_admin_a1b2c3d4...", "permissions": ["clients.manage", "hosting.manage"]}
admins.manage PermissionCreating or deleting Admin API tokens via POST /api/admin/api-tokens requires the acting admin to hold the admins.manage permission (or the wildcard * / all). super_admin accounts always pass this check. system_admin and support_admin roles do not hold this permission by default.
Token Format
Admin tokens are prefixed with mp_admin_ (e.g. mp_admin_a1b2c3...). The prefix is used at authentication time to route the request to the correct token table. The raw hex suffix is SHA-256 hashed before storage — only shown once on creation.
Token Lifetime
Admin tokens have no expiry by default (the expires_at column is NULL unless explicitly set). Revoke with DELETE /api/admin/api-tokens/{id}. last_used_at is updated on every successful request.
Authentication Method
# Bearer header (only supported method for admin tokens)
Authorization: Bearer mp_admin_YOUR_API_KEY
# X-API-Key header also accepted
X-API-Key: mp_admin_YOUR_API_KEY
# Query parameter also accepted
GET /api/admin/dashboard?api_key=mp_admin_YOUR_API_KEY
Permission Scopes (for API Token Access Control)
When creating an Admin API token you can restrict it to a subset of operations by passing a permissions array. The available permission strings are:
| Permission | Grants Access To | Default for Role |
|---|---|---|
* or all | Full admin access (wildcard) | super_admin |
clients.manage | Create, update, delete, impersonate clients | system_admin |
clients.read | Read-only access to client data | support_admin |
hosting.manage | Provision, suspend, unsuspend, change plans | system_admin |
hosting.read | Read-only access to hosting accounts | support_admin |
dns.manage | DNS settings, domain operations | system_admin |
dns.read | Read-only DNS data | support_admin |
system.manage | Storage, networking, nodes, agent, status page | system_admin |
admins.manage | Create admins, manage API tokens, manage 2FA | super_admin only |
plans.manage | Create and update hosting plans and reseller plans | system_admin |
impersonate | Login-as a client or reseller | super_admin only |
billing.manage | Billing-related operations | system_admin |
A token's effective permission is the intersection of the token's own permissions array and the admin's role permissions. Granting ["*"] on a support_admin token still limits it to the role's base permissions.
Managing Admin API Tokens via API
| Method & Endpoint | Description |
|---|---|
GET /api/admin/api-tokens | List tokens belonging to the authenticated admin |
POST /api/admin/api-tokens | Create a new token — admins.manage required |
DELETE /api/admin/api-tokens/{id} | Revoke a token by ID — admins.manage required |
Admin API Endpoint Reference
| Method & Endpoint | Description | Parameters & Payload Fields |
|---|---|---|
POST /api/admin/auth/login |
Authenticate admin and obtain a session token |
email (string, required)password (string, required)
|
POST /api/admin/auth/totp/verify |
Verify TOTP 2FA code to complete login | code (string, 6-digit TOTP) |
POST /api/admin/auth/logout |
Invalidate the current admin session | None |
GET /api/admin/dashboard |
Admin dashboard overview with counts, jobs, and system stats | None |
GET /api/admin/users |
List all client users | None |
GET /api/admin/account-stacks |
List all account Docker Compose stacks with runtime state | None |
GET /api/admin/clients |
List all clients with hosting account details | None |
POST /api/admin/clients |
Create a new client account |
email (string, required)password (string, min 8 chars)full_name (string, optional)
|
PATCH /api/admin/clients/{id} |
Update client email, name, or status |
email (string, optional)full_name (string, optional)status ("active" | "suspended")
|
DELETE /api/admin/clients/{id} |
Delete a client and all associated resources | None |
POST /api/admin/clients/{id}/login-as |
Impersonate a client and get a redirect URL to their panel | None |
GET /api/admin/hosting-accounts |
List all hosting accounts with plan and node details | None |
POST /api/admin/hosting-accounts |
Provision a new hosting account for a client |
user_id (int, required)plan_id (int, required)node_id (int, required)
|
POST /api/admin/hosting-accounts/{id}/suspend |
Suspend a hosting account | None |
POST /api/admin/hosting-accounts/{id}/unsuspend |
Unsuspend a previously suspended hosting account | None |
PATCH /api/admin/hosting-accounts/{id}/plan |
Change a hosting account's plan | plan_id (int, required) |
PATCH /api/admin/hosting-accounts/{id}/dns-provider |
Override DNS provider for a specific hosting account |
dns_provider (string: "local-dev-dns" | "cloudflare")dns_provider_account_id (int, optional)
|
GET /api/admin/plans |
List all hosting plans | None |
POST /api/admin/plans |
Create a new hosting plan |
name (string, required)storage_mb (int)memory_mb (int)cpu_limit (float)max_websites (int)max_databases (int)max_mailboxes (int)max_cron_jobs (int)dns_default_provider (string)
|
PATCH /api/admin/plans/{id} |
Update an existing hosting plan |
Same fields as POST plus:apply_to_existing_accounts (bool)migrate_existing_domains (bool)
|
POST /api/admin/plans/{id}/recalculate_usage |
Queue usage recalculation job for a specific plan | None |
POST /api/admin/plans/recalculate_usage |
Queue usage recalculation for all plans (or specific plan) | plan_id (int, optional) |
GET /api/admin/nodes |
List all registered server nodes | None |
POST /api/admin/nodes |
Register a new server node |
name (string, required)hostname (string, required)quota_backend (string, default "dev-simulator")
|
GET /api/admin/admins |
List all admin accounts | None |
POST /api/admin/admins |
Create a new admin account |
email (string, required)password (string, min 8 chars)full_name (string, optional)role ("support_admin" | "system_admin" | "super_admin")
|
POST /api/admin/admins/{id}/reset-password |
Reset an admin account's password | password (string, required) |
POST /api/admin/admins/{id}/disable-2fa |
Disable TOTP 2FA for an admin account | None |
POST /api/admin/admins/{id}/enable-2fa |
Enable and re-generate TOTP secret for an admin | None |
GET /api/admin/api-tokens |
List Admin API tokens for the authenticated admin | None |
POST /api/admin/api-tokens |
Generate a new Admin API token |
name (string, required)permissions (array of strings, default ["*"])
|
DELETE /api/admin/api-tokens/{id} |
Revoke an Admin API token | None |
GET /api/admin/reseller-plans |
List all reseller plans | None |
POST /api/admin/reseller-plans |
Create a new reseller plan |
name (string, required)max_storage_mb (int, default 50000)max_clients (int, default 10)max_hosting_accounts (int, default 20)max_ram_mb (int, default 8192)max_websites (int, default 50)max_databases (int, default 50)max_subplans (int, default 10)
|
PATCH /api/admin/reseller-plans/{id} |
Update a reseller plan | Same fields as POST |
DELETE /api/admin/reseller-plans/{id} |
Delete a reseller plan | None |
GET /api/admin/reseller-users |
List all reseller users | None |
POST /api/admin/reseller-users |
Create a new reseller user |
email (string, required)password (string, min 8 chars)full_name (string, optional)reseller_plan_id (int, optional)
|
PATCH /api/admin/reseller-users/{id} |
Update reseller user status, name, or plan |
status ("active" | "suspended")full_name (string, optional)reseller_plan_id (int, optional)
|
DELETE /api/admin/reseller-users/{id} |
Delete a reseller user account | None |
POST /api/admin/reseller-users/{id}/login-as |
Impersonate a reseller and get a redirect URL to their panel | None |
GET /api/admin/dns-settings |
Get global DNS provider configuration | None |
PATCH /api/admin/dns-settings |
Update global DNS provider settings | DNS provider configuration payload |
GET /api/admin/domains |
List all domains across all accounts | None |
POST /api/admin/domains |
Add a domain for a client account |
user_id (int, required)domain (string, required)account_id (int, optional)register (bool, trigger registrar registration)registrar_provider_id (int, optional)
|
POST /api/admin/domains/{id}/dns/rebuild |
Rebuild and publish the authoritative DNS zone for a domain | None |
POST /api/admin/domains/{id}/dns/verify-nameservers |
Verify nameserver propagation via public DNS | None |
GET /api/admin/domains/{id}/dns/export |
Export DNS zone in BIND format | None |
POST /api/admin/domains/{id}/dns/migrate-provider |
Migrate a domain to a different DNS provider |
dns_provider (string: "local-dev-dns" | "cloudflare")dns_provider_account_id (int, optional)
|
POST /api/admin/domains/dns/bulk-migrate-provider |
Bulk migrate all (or selected) domains to a new DNS provider |
dns_provider (string, required)domain_ids (array of ints, optional)all (bool, migrate all domains)
|
POST /api/admin/domains/{id}/registrar-nameservers |
Update domain nameservers via the registrar API | nameservers (array of 2–4 nameserver strings) |
GET /api/admin/registrars |
List all configured domain registrar providers | None |
POST /api/admin/registrars/{key} |
Configure a registrar provider (e.g. resellerclub) |
api_key (string, required)settings (object, provider-specific)reseller_id (string, required for ResellerClub)
|
POST /api/admin/dns/sync-cloudflare-rules |
Sync and ensure missing Cloudflare ACME challenge page rules system-wide | website_id (int, optional)account_id (int, optional) |
POST /api/admin/dns-providers/cloudflare/accounts |
Add a Cloudflare account for DNS management |
display_name (string, required)api_token (string, required)account_name (string, optional)external_account_id (string, optional)
|
PATCH /api/admin/dns-providers/cloudflare/accounts/{id} |
Update a Cloudflare account configuration |
display_name (string)api_token (string, optional – rotates token)status ("active" | "disabled")
|
DELETE /api/admin/dns-providers/cloudflare/accounts/{id} |
Remove a Cloudflare account (must not be in use by any plan) | None |
POST /api/admin/dns-providers/cloudflare/accounts/{id}/migrate-local |
Migrate all domains in a Cloudflare account to local PowerDNS | None |
POST /api/admin/dns-providers/{id}/test |
Test DNS provider connectivity and credentials | provider_account_id (int, optional) |
GET /api/admin/storage/df |
Get server disk filesystem usage summary | None |
GET /api/admin/storage/live |
Get live disk I/O read/write metrics | None |
GET /api/admin/storage/live/stream |
Server-Sent Events stream of live disk I/O metrics | None |
GET /api/admin/storage/quotas |
List per-account storage quota usage | None |
GET /api/admin/storage/paths |
Get size breakdown by major filesystem paths | None |
POST /api/admin/storage/cleanup |
Run storage cleanup for Docker, logs, and temp files |
clean_docker (bool, default true)clean_logs (bool, default true)clean_tmp (bool, default true)
|
GET /api/admin/storage/alerts |
Get storage alert threshold settings | None |
POST /api/admin/storage/alerts |
Update storage alert threshold settings | Storage alert configuration payload |
GET /api/admin/network/overview |
Network overview including interface stats | None |
GET /api/admin/network/live |
Live network I/O throughput metrics | None |
GET /api/admin/network/live/stream |
Server-Sent Events stream of live network I/O | None |
GET /api/admin/network/ips |
List all registered server IP addresses | None |
POST /api/admin/network/ips |
Add a new server IP address | IP address configuration payload |
PUT /api/admin/network/ips/{id} |
Update a server IP address record | IP address configuration payload |
DELETE /api/admin/network/ips/{id} |
Remove a server IP address record | None |
POST /api/admin/network/assign-account-ip |
Assign (or clear) a dedicated IP for a hosting account |
account_id (int, required)ip_id (int, optional – omit to clear)
|
GET /api/admin/jobs |
List recent background agent jobs (last 100) | None |
GET /api/admin/job-events |
List recent job event log entries (last 100) | None |
POST /api/admin/jobs/{id}/retry |
Retry a failed or queued agent job | None |
POST /api/admin/agent/run-once |
Trigger the agent to process one pending job | None |
POST /api/admin/agent/run-all |
Trigger the agent to process all pending jobs | None |
GET /api/admin/security/audit |
Run and return a live server security audit report | None |
GET /api/admin/audit-logs |
List audit log entries (last 100) | None |
GET /api/admin/system/default-page |
Get the default landing page HTML content | None |
POST /api/admin/system/default-page |
Set custom default landing page HTML content | default_page_content (string HTML, required) |
POST /api/admin/system/default-page/reset |
Reset the default landing page to the system default | None |
GET /api/admin/status |
Get the public status page payload including health checks | None |
POST /api/admin/status/incidents |
Create a new status incident |
title (string, required)severity ("minor" | "major" | "critical")state ("investigating" | "identified" | "monitoring" | "resolved")message (string, optional)published (bool, default true)
|
GET /api/admin/status/incidents |
List all status incidents with their update history | None |
PATCH /api/admin/status/incidents/{id} |
Update an incident's title, severity, state, or visibility |
title (string)severity (string)state (string)published (bool)
|
DELETE /api/admin/status/incidents/{id} |
Delete a status incident and all its updates | None |
POST /api/admin/status/incidents/{id}/updates |
Add an update to an existing incident |
state (string, required)message (string, optional)
|
POST /api/admin/status/maintenance |
Schedule a maintenance window |
title (string, required)state ("scheduled" | "in_progress" | "completed")starts_at (datetime string)ends_at (datetime string)message (string, optional)
|
PATCH /api/admin/status/maintenance/{id} |
Update the state or message of a maintenance window |
state (string, optional)message (string, optional)
|
POST /api/admin/status/components |
Update a status page component's health status |
component_id (int or string)status ("operational" | "degraded" | "partial_outage" | "major_outage")
|
PATCH /api/admin/status/components/{id} |
Update a specific status component by ID | status (string, required) |
POST /api/admin/status/checks/{id}/run |
Manually trigger a status health check | None |
Reseller API
The Reseller API is available on port 8002 (or the configured reseller port) and is accessible to reseller accounts. All endpoints are prefixed with /api/reseller/. Authenticate using a long-lived Reseller API token.
API Key Guide — Reseller Panel
How to Generate a Reseller API Key
Reseller API keys are scoped to the reseller user account. There are two ways to create one:
- In the UI: Log in to the Reseller Panel (port
8002), go to Settings > API Tokens (shown under the Admins page), click Generate Token, and enter a name. - Via API (requires an active reseller session or existing reseller token):
Response (token is shown only once):
POST /api/reseller/api-tokens Content-Type: application/json Authorization: Bearer mp_reseller_EXISTING_TOKEN {"name": "Provisioning Bot", "permissions": ["*"]}{"token_id": 2, "token": "mp_reseller_a1b2c3d4...", "name": "Provisioning Bot"}
Token Format
Reseller tokens are prefixed with mp_reseller_ (e.g. mp_reseller_a1b2c3...). The prefix routes the token to the reseller_api_tokens table at authentication time. The raw 20-byte hex suffix is SHA-256 hashed before storage — only shown once on creation.
Token Lifetime
Reseller tokens are created with an expiry of 365 days from creation (expires_at = now + 31536000). After expiry the server returns 401 reseller_api_token_expired. Generate a new token to replace an expired one.
Authentication Methods
# Bearer header (recommended)
Authorization: Bearer mp_reseller_YOUR_API_KEY
# X-API-Key header also accepted
X-API-Key: mp_reseller_YOUR_API_KEY
# Query parameter also accepted
GET /api/reseller/dashboard?api_key=mp_reseller_YOUR_API_KEY
Permission Scopes
When creating a Reseller API token you can pass a permissions array to narrow its scope. Reseller tokens do not map to named roles — the permissions field is stored as a JSON array and compared directly. Use ["*"] for full reseller access.
Regardless of the token's permission array, all reseller routes are automatically scoped to resources owned by that reseller. A reseller token can never read or modify another reseller's clients, plans, or accounts. The reseller_id from the authenticated token is injected into every query.
Managing Reseller API Tokens via API
| Method & Endpoint | Description |
|---|---|
GET /api/reseller/api-tokens | List all tokens for the authenticated reseller |
POST /api/reseller/api-tokens | Create a new token — any authenticated reseller can do this |
DELETE /api/reseller/api-tokens/{id} | Revoke a token by ID |
Unmatched reseller routes automatically fall through to the Admin API handler (with the reseller acting as a scoped admin). This means most admin-level read operations are also accessible via the /api/reseller/ prefix.
Reseller API Endpoint Reference
| Method & Endpoint | Description | Parameters & Payload Fields |
|---|---|---|
POST /api/reseller/auth/login |
Authenticate reseller and obtain a session token |
email (string, required)password (string, required)
|
POST /api/reseller/auth/totp/verify |
Verify TOTP 2FA code to complete reseller login | code (string, 6-digit TOTP) |
POST /api/reseller/auth/logout |
Invalidate the current reseller session | None |
GET /api/reseller/dashboard |
Reseller dashboard overview with sub-client, account, and storage stats | None |
GET /api/reseller/users |
List all sub-clients created by this reseller | None |
GET /api/reseller/account-stacks |
List all hosting accounts belonging to reseller's sub-clients | None |
GET /api/reseller/job-events |
List recent background job events (last 50) | None |
GET /api/reseller/clients |
List all sub-clients with their hosting accounts | None |
POST /api/reseller/clients |
Create a new sub-client under this reseller |
email (string, required)password (string, min 8 chars)full_name (string, optional)
|
PATCH /api/reseller/clients/{id} |
Update a sub-client's status or name |
status ("active" | "suspended")full_name (string, optional)
|
DELETE /api/reseller/clients/{id} |
Delete a sub-client account | None |
GET /api/reseller/plans |
List all hosting plans created by this reseller | None |
POST /api/reseller/plans |
Create a new sub-plan (limits enforced by the reseller's master plan) |
name (string, required)storage_mb (int, ≤ master plan limit)memory_mb (int, ≤ master plan limit)max_websites (int, ≤ master plan limit)max_databases (int, ≤ master plan limit)max_mailboxes (int)max_cron_jobs (int)
|
POST /api/reseller/hosting-accounts |
Provision a hosting account for a sub-client using a reseller sub-plan |
user_id (int, must belong to this reseller)plan_id (int, must be a reseller sub-plan)
|
GET /api/reseller/storage/df |
Get aggregated storage usage across all reseller sub-accounts | None |
GET /api/reseller/storage/live |
Live disk I/O metrics scoped to reseller accounts | None |
GET /api/reseller/storage/quotas |
Per-account storage quota usage for reseller sub-accounts | None |
GET /api/reseller/storage/paths |
Size breakdown by major filesystem paths | None |
POST /api/reseller/storage/cleanup |
Run storage cleanup for Docker, logs, and temp files |
clean_docker (bool, default true)clean_logs (bool, default true)clean_tmp (bool, default true)
|
GET /api/reseller/storage/alerts |
Get storage alert threshold settings | None |
GET /api/reseller/network/overview |
Network overview for reseller-scoped accounts | None |
GET /api/reseller/network/live |
Live network I/O metrics scoped to reseller accounts | None |
GET /api/reseller/network/ips |
List all server IP addresses | None |
GET /api/reseller/dns-settings |
Get global DNS provider configuration | None |
GET /api/reseller/domains |
List all domains belonging to reseller sub-clients | None |
GET /api/reseller/dns-providers/cloudflare/accounts |
List Cloudflare DNS accounts visible to this reseller | None |
GET /api/reseller/registrars |
List domain registrar providers | None |
GET /api/reseller/security/audit |
Run and return a live server security audit report | None |
GET /api/reseller/admins |
Get the reseller's own profile and API token list | None |
GET /api/reseller/api-tokens |
List Reseller API tokens | None |
POST /api/reseller/api-tokens |
Generate a new Reseller API token |
name (string, required)permissions (array of strings, default ["*"])
|
DELETE /api/reseller/api-tokens/{id} |
Revoke a Reseller API token | None |
GET /api/reseller/status |
Get the current system and service status payload | None |
GET /api/reseller/default-page |
Get the default landing page content | None |