Developer API

A REST API to connect Koapanel to any system

Besides the WHMCS module, every Koapanel server exposes a public API, versioned and documented as OpenAPI 3.1: create and suspend accounts, manage sites, databases and mailboxes, receive signed webhooks. For any billing system, home-made ones included.

Try it on the online demo: login admin / demo-admin-2026, data resets every hour; create a key in API keys.

On this page

At a glance

Address https://<your-server>:8443/api/v1
Specification GET /api/v1/openapi.json (OpenAPI 3.1, public) and in the panel: API keys › API documentation
Authentication Authorization: Bearer kpk_… — personal API key of an administrator or reseller
Format JSON, UTF-8
Errors {"error":{"code":"…","message":"…","field":"…","requestId":"…"}}
Pagination ?limit= (1–500) and ?cursor=; the answer carries nextCursor while more pages follow
Idempotency Idempotency-Key header on every POST, 24 hours
Limits 300 requests per minute per key, X-RateLimit-* headers
Webhooks events signed with HMAC-SHA256, retried up to 5 times
Compatibility within /api/v1 fields and operations are only added, never removed or changed

The API key

In the panel, as an administrator or reseller: API keys › Create key. The key (kpk_…) is shown once and acts as its account:

  • a reseller's key sees and changes only the reseller's clients and packages, within its quota;
  • creating resellers needs an administrator's key;
  • if the account is suspended or deleted, its keys stop working;
  • every operation shows in the Activity log with the name of the key.

What you can do

Area Operations
Identity GET /auth/me, POST /auth/login-link
Hosting accounts list, create, read, change (email, package), delete, suspend, unsuspend, password, one-time login link
Resellers list, create with a quota, change the quota
Packages list, create, change, delete
Usage disk used per account
Sites list, create, read, PHP and additional domains, delete
Databases list, create, users, passwords, delete
Mail mail domains, mailboxes (create, quota, forwarding, password, delete)
Webhooks register, change, test, deliveries, delete

The complete list with every field is in the OpenAPI document of the server.

Examples

curl

export KP=https://server1.example.com:8443/api/v1
export KEY=kpk_...   # API keys › Create key
# an account with the password chosen by your system, safe to repeat
curl -sS -X POST "$KP/accounts" \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-H "Idempotency-Key: order-10452" \
-d '{"username":"mario","email":"mario@example.com","package":"pkg_base","passwordMode":"set","password":"A-Strong-Password-2026"}'
# suspension for an overdue invoice, then reactivation
curl -sS -X POST "$KP/accounts/mario/suspend" -H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" -d '{"reason":"Invoice 2026/118 overdue"}'
curl -sS -X POST "$KP/accounts/mario/unsuspend" -H "Authorization: Bearer $KEY"
# one-time login link (60 seconds) that opens the file manager
curl -sS -X POST "$KP/accounts/mario/login-link" -H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" -d '{"next":"/files"}'

PHP

<?php
$base = 'https://server1.example.com:8443/api/v1';
$key  = getenv('KOAPANEL_API_KEY');
function kp(string $method, string $path, ?array $body = null, array $headers = []): array {
global $base, $key;
$ch = curl_init($base . $path);
$h = ['Authorization: Bearer ' . $key, 'Accept: application/json'];
foreach ($headers as $k => $v) { $h[] = "$k: $v"; }
if ($body !== null) { $h[] = 'Content-Type: application/json'; curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body)); }
curl_setopt_array($ch, [CURLOPT_CUSTOMREQUEST => $method, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => $h, CURLOPT_TIMEOUT => 30]);
$raw = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
$data = $raw === '' ? [] : json_decode($raw, true);
if ($code >= 400) { throw new RuntimeException($data['error']['message'] ?? "HTTP $code", $code); }
return $data ?? [];
}
// every account, page by page
$cursor = null;
do {
$page = kp('GET', '/accounts?limit=100' . ($cursor ? '&cursor=' . urlencode($cursor) : ''));
foreach ($page['accounts'] as $a) { echo $a['username'], ' ', $a['usage']['diskBytes'], "\n"; }
$cursor = $page['nextCursor'] ?? null;
} while ($cursor);
// a database for the customer's site
$db = kp('POST', '/databases', ['name' => 'mario_shop', 'site' => 'mario.example.com'], ['Idempotency-Key' => 'order-10452-db']);
echo $db['credentials']['user'], ' / ', $db['credentials']['password'], "\n";

Python

import os, requests
BASE = "https://server1.example.com:8443/api/v1"
S = requests.Session()
S.headers["Authorization"] = "Bearer " + os.environ["KOAPANEL_API_KEY"]
def kp(method, path, **kw):
r = S.request(method, BASE + path, timeout=30, **kw)
if r.status_code == 429:
raise RuntimeError(f"rate limit, retry in {r.headers['Retry-After']} s")
if r.status_code >= 400:
raise RuntimeError(r.json()["error"]["message"])
return r.json() if r.content else None
site = kp("POST", "/sites", json={"domain": "mario.example.com", "owner": "mario"},
headers={"Idempotency-Key": "order-10452-site"})
box = kp("POST", "/mailserver/domains/mario.example.com/mailboxes", json={"local": "info", "quotaMB": 2048})
print(box["address"], box.get("password"))  # a generated password is shown once

Webhooks

Register the address of your system (API keys › Webhooks, or POST /webhooks) and receive a POST request for every event:

Event When
account.created / account.updated / account.deleted account created, changed (email, package), deleted
account.suspended / account.unsuspended suspension and reactivation
account.usage_over_quota disk used reaches the package limit (once per crossing)
reseller.created new reseller
site.created / site.deleted site created or deleted
ping test from the panel

The body is {"id":"evt_…","type":"account.suspended","createdAt":"…","data":{…}} with the headers X-Koapanel-Event, X-Koapanel-Delivery and X-Koapanel-Signature: t=<seconds>,v1=<signature>, where the signature is the hex HMAC-SHA256 of "<t>.<body>" with the webhook secret (shown once, at creation). Answer 2xx within 10 seconds; otherwise the panel retries after 10 s, 1 min, 5 min, 30 min and 2 h. Administrators receive every event, resellers only their clients' events (and only to public https:// addresses).

Verification in PHP:

$body = file_get_contents('php://input');
$sig  = $_SERVER['HTTP_X_KOAPANEL_SIGNATURE'] ?? '';
parse_str(str_replace(',', '&', $sig), $p);   // t=…, v1=…
$ok = isset($p['t'], $p['v1']) && abs(time() - (int)$p['t']) < 300
&& hash_equals(hash_hmac('sha256', $p['t'] . '.' . $body, getenv('KOAPANEL_WEBHOOK_SECRET')), $p['v1']);
if (!$ok) { http_response_code(400); exit; }
$event = json_decode($body, true);

Verification in Python:

import hmac, hashlib, time
def verify(secret: str, header: str, body: bytes, tolerance=300) -> bool:
parts = dict(x.split("=", 1) for x in header.split(","))
if abs(time.time() - int(parts.get("t", 0))) > tolerance:
return False
mac = hmac.new(secret.encode(), parts["t"].encode() + b"." + body, hashlib.sha256).hexdigest()
return hmac.compare_digest(mac, parts.get("v1", ""))

Ready-made modules

  • WHMCS 8.x and 9.x: official module (accounts, resellers, one-click login, licences for partners) — see the "WHMCS integration" page.
  • Planned: modules for Blesta and HostBill, and FOSSBilling. Meanwhile these systems can use the API above (the operations are the same as the WHMCS module's).

Partner API: reselling Koapanel licences

For providers selling Koapanel licences to their own customers, with the WHMCS module koapanel_license or any other billing system. It is not the panel API: it is the API of the Koapanel console, with a partner key we give you.

Base URL https://console.koapanel.app/api/v1/partner
Specification /api/v1/partner/openapi.json (OpenAPI 3.1, public)
Authentication Authorization: Bearer kpp_… — partner key, shown once; rotation with 24 hours of overlap
Errors {"error":{"code":"…","message":"…"}}, messages in Italian
Idempotency Idempotency-Key on create (7 days); externalId unique among non-revoked licences
Limits 120 requests per minute per key
Audit every operation is logged with the partner, the key and the client (X-Panel-Client)
Operation Request
Partner and enabled plans GET /me, GET /plans
List licences GET /licenses?externalId=&status=&page=
Create a licence POST /licenses with plan, externalId, customer (name, email, country)
One licence with its activation code GET /licenses/{id}
Suspend, reactivate, revoke POST /licenses/{id}/suspend, /reactivate (or /unsuspend), /revoke (or /terminate)
Change plan POST /licenses/{id}/plan with plan
Free from its server (to move it) POST /licenses/{id}/release — at most 3 times in 30 days
New code POST /licenses/{id}/reissue
Usage of the month, statements GET /usage?month=YYYY-MM, GET /statements, GET /statements/{month}
export KPP=kpp_...   # the partner key
curl -sS -X POST https://console.koapanel.app/api/v1/partner/licenses \
-H "Authorization: Bearer $KPP" -H "Content-Type: application/json" \
-H "Idempotency-Key: order-10452" \
-d '{"plan":"pro","externalId":"order-10452","customer":{"name":"Bianchi Ltd","email":"it@bianchi.example","country":"GB"}}'

The answer carries license.id and license.token: the customer pastes the code in Koapanel › Plan and licence, and the licence binds to that server. Partner licences never show Koapanel's own plans and checkout.

Wholesale billing. Each licence costs the monthly partner price of its plan, pro rata for its time in the month (plan changes count from the moment of the change; a suspended licence is billed, a revoked one until the end of the day of revocation). At the start of each month you receive the electronic invoice for the previous month, with the detail per licence in GET /statements/{month}. To become a partner, write to us.

OpenAPI 3.1 specification of the Partner API · Panel OpenAPI on the demo