mdestafadilah / aapanelapi
PHP client for the aaPanel panel API v2 (websites, domains, SSL, MySQL databases and system services). Maintained fork of mythicalsystems/aapanelapi.
Requires
- php: >=7.4
- ext-curl: *
Requires (Dev)
None
Suggests
- guzzlehttp/guzzle: Optional transport (^7.0) if you prefer Guzzle over the bundled cURL transport
Provides
None
Conflicts
None
Replaces
None
README
A small PHP client for the aaPanel panel API v2: websites (PHP projects), domains, SSL certificates, files, MySQL databases and system services (Nginx, MySQL, PHP-FPM, Redis, Apache).
No framework, no required third-party HTTP library — plain cURL by default, Guzzle optional.
Fork. This is a maintained fork of AAPanel-PHP, the aaPanel client started by skolastikarhea in 2022 and later developed by NaysKutzu / MythicalSystems. Version 2.0.0 rewrites the request layer for panel API v2. See credits for the full lineage and licence holders.
use MythicalSystems\AaPanelApi\AaPanel; $aapanel = new AaPanel('http://1.2.3.4:7800', 'YOUR_API_KEY'); foreach ($aapanel->siteList()['data'] as $site) { echo $site['name'], PHP_EOL; }
Requirements
- PHP 7.4+ with
ext-curl - aaPanel with the API enabled
- The IP you call from added to the panel API whitelist
Install
composer require mdestafadilah/aapanelapi
Optional, if you prefer Guzzle over the bundled cURL transport:
composer require guzzlehttp/guzzle
Enabling the API in aaPanel
- Panel settings → API → enable
- Generate an API key
- Add your public IP to the whitelist
- Save
Quick start
use MythicalSystems\AaPanelApi\AaPanel; $aapanel = new AaPanel('http://1.2.3.4:7800', 'YOUR_API_KEY'); // Self-signed panel certificate? Turn verification off explicitly. $aapanel->setVerifySsl(false); // Or configure step by step $aapanel->setUrl('http://1.2.3.4:7800') ->setKey('YOUR_API_KEY') ->setTimeout(60); // Swap the transport if the project already ships Guzzle $aapanel->setTransport(new \MythicalSystems\AaPanelApi\Http\GuzzleTransport());
Every method returns the panel's message payload, or throws AaPanelException.
What changed in aaPanel API v2
The v1 calls this library used to make no longer match the panel. Four things moved:
| v1 (old library) | v2 (this version) | |
|---|---|---|
| Base path | /site?action=AddSite |
/v2/site?action=AddSite |
| Auth | request_time + request_token in the POST body |
both in the query string |
| Response | raw payload | {"status":0,"timestamp":…,"message":…} |
| Session | cookie jar written to the working directory | stateless, no cookies |
The token formula is unchanged — md5(request_time . md5(api_key)) — but both values must come from the same timestamp. The old code called time() twice, which could straddle a second boundary and produce a token the panel rejects. AaPanel::sign() now captures the timestamp once.
Per-endpoint contract changes worth knowing:
- AddSite —
typeisPHP, andis_create_default_fileis a new required field.ftpandsqlare booleans. - DeleteSite — only
id+webname; the oldftp/database/pathflags are gone. UsecheckDeleteSite()first to see what a deletion would remove. - SaveFileBody —
encodingis required. - AddDatabase — access rules are sent as
dataAccessandaddress. - SSL —
apply_cert_api+SetSSLwere replaced by a certificate store:uploadCertificate()returns a hash,deployCertificate()pushes it to domains. - getData — the spec puts
tablein the query for some tables (domain,databases) and in the body for others (sites,backup). The panel merges$_REQUEST, so the helpers send it in both places.
Migrating from 1.x
- use mythicalsystems\aapanel\aapanel; + use MythicalSystems\AaPanelApi\AaPanel; - $aapanel = new aapanel; - $aapanel->key = 'YOUR_API_KEY'; - $aapanel->url = 'http://1.2.3.4:7800'; + $aapanel = new AaPanel('http://1.2.3.4:7800', 'YOUR_API_KEY');
Renamed or changed methods:
| 1.x | 2.x |
|---|---|
siteList($limit, $page, $projectType, $search) |
siteList($limit, $page, $search, $type) — PHP projects only; nodejsProjects() and pm2Projects() are separate |
deleteSite($webname, $id) |
deleteSite($id, $webname) |
safeFileBody($data, $path) |
saveFileBody($path, $data, $encoding = 'utf-8') |
importDbase($file, $dbasename) |
importSql($file, $dbName) |
applySSL($domain, $id) |
installCertificate($siteName, $key, $cert) |
forceHTTPS($siteName) |
forceHttps($siteName) (deprecated) |
addSite(..., $ftp, $ftpUser, $ftpPass, $sql, $dbUser, $dbPass, ...) |
addSite(...) — FTP/database are created when you pass the credentials |
Return values are now the message payload instead of the whole envelope. Pass true as the fourth argument of request() when you want the envelope back.
Method reference
Client
| Method | Endpoint |
|---|---|
request($endpoint, $query, $body, $rawEnvelope) |
any /v2/… endpoint |
getData($table, $params, $query) |
/v2/data?action=getData |
getKey($table, $key, $id) |
/v2/data?action=getKey |
logs($limit, $page) |
/v2/data?action=getData (table=logs) |
Websites
| Method | Endpoint |
|---|---|
siteList($limit, $page, $search, $type) |
/v2/data?action=getData (table=sites) |
siteGroups() |
/v2/site?action=get_site_types |
addSite(...) |
/v2/site?action=AddSite |
checkDeleteSite($ids) |
/v2/site?action=check_del_data |
deleteSite($id, $webname, $extra) |
/v2/site?action=DeleteSite |
siteDomains($siteId) |
/v2/data?action=getData&table=domain |
addDomain($domain, $webname, $id) |
/v2/site?action=AddDomain |
deleteDomain($id, $webname, $domain, $port) |
/v2/site?action=DelDomain |
siteUserIni($path, $id) |
/v2/site?action=GetDirUserINI |
toggleSiteUserIni($path, $id) |
/v2/site?action=SetDirUserINI |
sitePath($id) |
/v2/data?action=getKey (sites.path) |
setSiteRunPath($id, $runPath) |
/v2/site?action=SetSiteRunPath |
sitePhpVersion($siteName) |
/v2/site?action=GetSitePHPVersion |
phpVersions() |
/v2/site?action=GetPHPVersion |
setSitePhpVersion($siteName, $version) |
/v2/site?action=SetPHPVersion |
rewriteTemplates($siteName) |
/v2/site?action=GetRewriteList |
disableSite($id, $domain) / enableSite(...) |
/v2/site?action=SiteStop / SiteStart |
nodejsProjects() / pm2Projects() |
/v2/project/nodejs/get_project_list, /v2/plugin?action=a&s=List&name=pm2 |
SSL
| Method | Endpoint |
|---|---|
siteSsl($siteName) |
/v2/site?action=GetSSL |
closeSsl($siteName, $updateOf) |
/v2/site?action=CloseSSLConf |
uploadCertificate($key, $cert) |
/v2/ssl_domain?action=upload_cert |
deployCertificate($hash, $domains, $append) |
/v2/ssl_domain?action=cert_deploy_sites |
installCertificate($siteName, $key, $cert, $domains) |
upload + deploy |
certificates($page, $limit) |
/v2/ssl_domain?action=list_ssl_info |
Databases (MySQL)
| Method | Endpoint |
|---|---|
databaseList($page, $limit, $search) |
/v2/data?action=getData&table=databases&type=MySQL |
addDatabase($name, $user, $password, $access, …) |
/v2/database?action=AddDatabase |
checkDeleteDatabase($ids) |
/v2/database?action=check_del_data |
deleteDatabase($id, $name) |
/v2/database?action=DeleteDatabase |
setDatabasePassword($id, $password, $name) |
/v2/database?action=ResDatabasePassword |
getDatabaseAccess($name) |
/v2/database?action=GetDatabaseAccess |
setDatabaseAccess($name, $access) |
/v2/database?action=SetDatabaseAccess |
databaseInfo($dbName) |
/v2/database?action=GetInfo |
optimizeTables($dbName, $tables) |
/v2/database?action=OpTable |
repairTables($dbName, $tables) |
/v2/database?action=ReTable |
backupDatabase($id) |
/v2/database?action=ToBackup |
backupList($databaseId, $page, $limit) |
/v2/data?action=getData (table=backup) |
deleteBackup($id) |
/v2/database?action=DelBackup |
importSql($file, $dbName) |
/v2/database?action=InputSql |
databaseQuota($dbName, $sizeMb) |
/v2/project/quota/modify_database_quota |
mysqlStatus() |
/v2/panel/public/get_soft_status?name=mysql |
mysqlRootPassword() / setMysqlRootPassword($password) |
/v2/data?action=getKey, /v2/database?action=SetupPassword |
syncToMysql($ids) / syncFromMysql($siteId) |
SyncToDatabases / SyncGetDatabases |
recycleBin() / restoreFromRecycleBin($rname) |
/v2/files?action=Get_Recycle_bin / Re_Recycle_bin |
Files and DNS
| Method | Endpoint |
|---|---|
getFileBody($path) / saveFileBody($path, $data, $encoding) |
/v2/files?action=GetFileBody / SaveFileBody |
setRewrite($siteName, $rules) |
SaveFileBody on /www/server/panel/vhost/rewrite/{site}.conf |
unzip($source, $destination, $password) |
/v2/files?action=UnZip |
addSubDomain() / deleteSubDomain() / modifySubDomain() / subDomainList() |
/v2/plugin?action=a&name=dns_manager&… |
Services
Start, stop, restart and reload the software aaPanel installed itself — Nginx, MySQL/MariaDB, PHP-FPM, Redis, Apache and OpenLiteSpeed. The panel runs /etc/init.d/<service> <action> for you, which is exactly what Service management commands documents.
| Method | Endpoint |
|---|---|
serviceAction($service, $action) |
/v2/system?action=ServiceAdmin |
startService($service) / stopService($service) |
same, type=start / stop |
restartService($service) / reloadService($service) |
same, type=restart / reload |
serviceConfigTest($service) |
same, type=test (the page calls it configtest) |
serviceStatus($name) |
/v2/panel/public/get_soft_status?name=… |
phpFpmService($version) |
8.2 → php-fpm-82 |
$aapanel->restartService('nginx'); $aapanel->reloadService($aapanel->phpFpmService('8.2')); // php-fpm-82 $aapanel->restartService('mysqld'); // MySQL has no reload
reloadService() and serviceConfigTest() refuse a service the official page does not document that action for, instead of letting the panel run an unknown init target and still answer "success": MySQL and Apache have no reload, OpenLiteSpeed turns any unknown action into a restart, and only Nginx and PHP-FPM document a config test. serviceAction() stays raw for everything else.
A broken web server configuration is caught by the panel before it starts anything — the exception carries the nginx -t / apachectl -t output.
Service catalog. ServiceCatalog holds the documented init scripts, commands and paths. It makes no API call, so it is also useful over SSH, in cron and in runbooks:
use MythicalSystems\AaPanelApi\ServiceCatalog; ServiceCatalog::command('nginx', 'reload'); // /etc/init.d/nginx reload ServiceCatalog::command('php-fpm-8.2', 'configtest'); // /etc/init.d/php-fpm-82 configtest ServiceCatalog::path('nginx', 'config_file'); // /www/server/nginx/conf/nginx.conf ServiceCatalog::path('php-fpm-82', 'log'); // /www/server/php/82/var/log/php-fpm.log ServiceCatalog::actions('redis'); // start, stop, restart, reload, status
Names are the ones the panel uses (mysqld, httpd, php-fpm-82, lsphp74). The aliases mysql, apache, openresty and tengine resolve to the same entries, and PHP versions are accepted with or without the dot (8.2, 82, php-fpm-8.2).
Error handling
use MythicalSystems\AaPanelApi\AaPanelException; try { $aapanel->addSite('test31.com', 'test31.com'); } catch (AaPanelException $e) { $e->getMessage(); // panel message, e.g. "Site already exists" $e->getPanelStatus(); // panel status field, -1 for transport failures $e->getResponseBody(); // raw response for logging }
status != 0 from the panel, HTTP errors, non-JSON answers (wrong URL, API disabled, IP not whitelisted) and connection failures all raise AaPanelException.
Unverified endpoints
The provided v2 specification covers the website and MySQL controllers only. These methods are carried over from v1 and are not covered by it — verify them against your panel build before relying on them:
disableSite / enableSite, nodejsProjects, pm2Projects, unzip, forceHttps, and everything in the DNS manager group.
Service management is not in that specification either. serviceAction and the startService / stopService / restartService / reloadService / serviceConfigTest wrappers call /v2/system?action=ServiceAdmin, which is verified against the panel source (class/system.py: it executes /etc/init.d/<name> <type>) but is not documented as an API. serviceStatus reuses the get_soft_status endpoint mysqlStatus already relies on; only name=mysql is covered by the specification, so other software names are a best guess.
Tests
composer test
tests/smoke.php runs 91 checks against a fake transport: request signing, the /v2 prefix, field names and types, the service catalog, and envelope/error handling. No panel needed.
Releasing
Bumping the version file and pushing a tag is the whole manual part:
git tag -a v2.1.0 -m "2.1.0 - service management"
git push origin v2.1.0
.github/workflows/release.yml then:
- runs
composer validate --strict, the linter and the smoke test on PHP 7.4 (the floor incomposer.json) and 8.3; - refuses to continue when the tag and the
versionfile disagree; - creates the GitHub release, with notes generated from the commits since the previous tag — a tag containing a suffix (
v2.2.0-rc1) is marked as a prerelease; - pings Packagist when a
PACKAGIST_TOKENsecret is configured. Without it Packagist still updates from its own GitHub webhook, just not immediately.
Original source & credits
This library was not written from scratch. It descends from a third-party aaPanel client that changed hands twice before the v2 rewrite:
| Year | Author | Where |
|---|---|---|
| 2022 | skolastikarhea | skolastikarhea/api_aapanel — the original "3rd party api aapanel" |
| 2022–2023 | mdestafadilah | this repository |
| 2023 | NaysKutzu / MythicalSystems | published as mythicalsystems/aapanelapi; its GitHub repository (MythicalLTD/AAPanel-API) is no longer available |
| 2026 | mdestafadilah | v2 rewrite of the request layer, published as mdestafadilah/aapanelapi |
The original package is marked abandoned on Packagist, so this fork continues it under a new vendor name instead of trying to take over the old one.
Kept from the original work: the overall shape of the client (one class, one method per panel action), the public method names wherever they still map to a live endpoint, and the MIT licence.
Replaced in 2.0.0: the transport (no more cookie jar, TLS verification on by default), request signing, response handling, and every endpoint path — see What changed in aaPanel API v2.
If you are one of the original authors and want this notice worded differently, open an issue.
License
MIT. Copyright (c) 2022 skolastikarhea, 2022–2023 mdestafadilah, 2023 MythicalSystems / NaysKutzu — see LICENSE.