API Reference
Sahaab has three programming interfaces:
| Interface | For | Address | Token made in |
|---|---|---|---|
| Billing-system interface | Billing systems (Blesta, HostBill and others) | https://your-server:2087/json-api/<function> | Admin panel → API Tokens |
| API v1 | Your own scripts and integrations, as admin or reseller | https://your-server:2087/api/v1/... | Admin panel → API Tokens |
| Customer API | A hosting customer's own scripts | https://your-server:2083/api/v1/me... | Control panel → API tokens |
All interfaces use HTTPS and answer in JSON.
Tokens and security
Create a token
- Administrators and resellers: admin panel → Account → API Tokens. Enter a name (for example
billing) and click Create token. - Customers: control panel → API tokens. Enter a name and click Create token.
Copy the token at once: it is shown only once. Click Revoke to cancel it. The token list shows when each token was last used.
Limit a token
When you create a token, you can limit it:
- Allowed IP addresses: the token works only from these addresses or ranges, for example
203.0.113.10, 198.51.100.0/24(IPv6 too). Leave it empty to allow any address. Use this for a billing server with a fixed address. - Expires: the token stops working after 7, 30, 90 or 365 days.
- Read only: the token can read but not change anything. On
/api/v1onlyGETrequests are allowed. On/json-apionlyversion,applist,listaccts,accountsummary,listpkgs,showbwandgethostnameare allowed.
A refused request says why: "This API token has expired.", "This API token may not be used from this address." or "This API token can only read."
Send the token
Send the token in the Authorization header:
Authorization: Bearer YOUR_TOKEN
Billing systems' standard hosting-panel modules send the username together with the token in their own header format. Sahaab accepts that too, so you only enter the username and token in the module.
Protection against guessing
Every request with a wrong token counts as a failed login. After too many failures, the sender's IP address is blocked (see Brute Force Protection in the admin panel). Add your billing server's IP address to the Firewall allow list so a mistake never blocks it.
Reseller tokens
A reseller's token works only on the reseller's own accounts and packages. It is also limited by the reseller's privileges. For example, a reseller without the Terminate accounts privilege cannot remove accounts through any API. See Reseller privileges.
| Action | Privilege needed |
|---|---|
| Create an account | Create accounts |
| Remove / terminate an account | Terminate accounts |
| Suspend or unsuspend | Suspend and unsuspend accounts |
| Change package, modify account | Modify accounts |
| Change password | Change account passwords |
| One-time sign-in link | Log in to customers' control panels |
Billing-system interface (json-api)
Sahaab answers the standard /json-api interface on port 2087 that hosting billing systems already use. Their usual server module for hosting control panels works without installing anything. For WHMCS, a ready module is available from API Tokens → Download the WHMCS module. See API tokens and billing systems for the setup steps.
Calling a function
GET or POST https://your-server:2087/json-api/<function>?api.version=1&<parameters>
- With
api.version=1, the answer is: ``json {"metadata": {"version": 1, "reason": "OK", "result": 1, "command": "listaccts"}, "data": {...}}`resultis1on success and0on failure. On failure,reason` explains why. - Without
api.version=1, the answer uses the older format that older modules expect.
Functions
| Function | What it does | Parameters |
|---|---|---|
createacct | Create an account | username, domain, plan (package name), contactemail, password |
suspendacct | Suspend an account | user, reason (optional) |
unsuspendacct | Unsuspend an account | user |
removeacct | Terminate an account | user |
changepackage | Change the package | user, pkg |
passwd | Change the password | user, password |
modifyacct | Change the contact email, main domain or username | user, and any of contactemail, domain, newuser |
listaccts | List accounts | optional search, searchtype (user, domain, owner, package, ip), searchmethod=exact |
accountsummary | One account's details | user |
listpkgs | List packages and their limits | — |
showbw | Bandwidth used per account in a month | optional month, year, search |
create_user_session | A one-time sign-in link to the customer's control panel (single use, valid 60 seconds) | user |
gethostname | The server hostname | — |
version | The panel version | — |
applist | The list of supported functions | — |
Notes:
- Package names must match a package in Sahaab exactly. For a reseller, the reseller's own packages are used. If the billing system adds the reseller's name in front (
reseller_Basic), that prefix is removed. - If
createaccthas noplan, the first package is used. If it has nocontactemail, the email of the token's owner is used. createacctreturns the account's IP address and nameservers.- Account details include the domain, contact email, package, owner, IP, suspension status and reason, disk used and limit, and the package limits (addon domains, subdomains, aliases, mailboxes, databases, FTP accounts).
- Changing the main domain or the username with
modifyacctworks like the admin panel: see Find and manage accounts.
Examples
Create an account:
curl -s -H "Authorization: Bearer $TOKEN" \
"https://server1.example.com:2087/json-api/createacct?api.version=1" \
--data-urlencode "username=shop" \
--data-urlencode "domain=shop-example.com" \
--data-urlencode "plan=Basic" \
--data-urlencode "[email protected]" \
--data-urlencode "password=Str0ng-Passw0rd!"
Suspend an account:
curl -s -H "Authorization: Bearer $TOKEN" \
"https://server1.example.com:2087/json-api/suspendacct?api.version=1&user=shop&reason=Unpaid+invoice"
Get a sign-in link for the customer:
curl -s -H "Authorization: Bearer $TOKEN" \
"https://server1.example.com:2087/json-api/create_user_session?api.version=1&user=shop"
The answer's data.url is a link that signs the customer in once, within 60 seconds.
API v1 for administrators and resellers
A simple JSON API for your own scripts and integrations. It uses the same tokens as the billing-system interface.
- Address:
https://your-server:2087/api/v1 - Authentication:
Authorization: Bearer YOUR_TOKEN - Request body: JSON (
Content-Type: application/json) or form fields. - Answers:
- success:
{"status": 1, "data": ...} - failure:
{"status": 0, "error": "..."} - HTTP codes:
401wrong token,403blocked address or missing privilege,422invalid input (theerrorsays why).
Endpoints
| Method and path | What it does | Body |
|---|---|---|
GET /api/v1/accounts | List accounts (optional ?search=) | — |
POST /api/v1/accounts | Create an account | username, domain, email, password, package (name) |
GET /api/v1/accounts/{username} | One account | — |
POST /api/v1/accounts/{username}/suspend | Suspend | reason (optional) |
POST /api/v1/accounts/{username}/unsuspend | Unsuspend | — |
POST /api/v1/accounts/{username}/password | Change the password | password |
POST /api/v1/accounts/{username}/package | Change the package | package (name) |
POST /api/v1/accounts/{username}/terminate | Terminate (cannot be undone) | — |
POST /api/v1/accounts/{username}/login | One-time sign-in link to the control panel (60 seconds) | — |
GET /api/v1/packages | List packages | — |
GET /api/v1/jobs/{id} | Status of a background task | — |
GET /api/v1/server/status | Server load, memory, disk, services and version (administrators only) | — |
/api/v1/dns/secondary is used between Sahaab servers for the DNS cluster. You do not call it yourself.
The account object
{
"username": "shop",
"domain": "shop-example.com",
"email": "[email protected]",
"package": "Basic",
"status": "active",
"suspend_reason": null,
"ip": null,
"disk_used_mb": 412,
"disk_limit_mb": 5120,
"bandwidth_used_mb": 1830,
"bandwidth_limit_mb": 51200,
"created_at": "2026-10-19 10:12:00"
}
bandwidth_used_mb is this month's use. A limit of 0 means unlimited. ip is set only when the account has a dedicated address.
Examples
List accounts:
curl -s -H "Authorization: Bearer $TOKEN" \
https://server1.example.com:2087/api/v1/accounts
Create an account:
curl -s -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"username":"shop","domain":"shop-example.com","email":"[email protected]","password":"Str0ng-Passw0rd!","package":"Basic"}' \
https://server1.example.com:2087/api/v1/accounts
Change the package:
curl -s -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"package":"Business"}' \
https://server1.example.com:2087/api/v1/accounts/shop/package
Get a one-time sign-in link:
curl -s -X POST -H "Authorization: Bearer $TOKEN" \
https://server1.example.com:2087/api/v1/accounts/shop/login
# {"status":1,"data":{"url":"https://..."}}
Customer API
A hosting customer can automate their own account: mailboxes, forwarders, databases, DNS records and cron jobs.
- Token: control panel → API tokens → Create token. Only the account owner can make tokens, not team members.
- Address:
https://your-server:2083/api/v1/me - Authentication:
Authorization: Bearer YOUR_TOKEN - Request body: JSON (
Content-Type: application/json) or form fields. - Answers:
{"status": 1, "data": ...}or{"status": 0, "error": "..."}.
Rules:
- Only the tools of the customer's plan are available. Otherwise the answer is
403"This tool is not included in your plan." - A suspended account gets
403"This account is suspended." - Invalid input gets
422with the reason.
Endpoints
| Method and path | What it does | Body |
|---|---|---|
GET /me | Account, package, disk use and limits | — |
GET /me/domains | Domains with type and folder | — |
GET /me/email | Mailboxes with quota and size | — |
POST /me/email | Create a mailbox | address, password, quota_mb (default 1024) |
POST /me/email/{address}/password | Change a mailbox password | password |
POST /me/email/{address}/delete | Delete a mailbox | — |
GET /me/forwarders | List forwarders | — |
POST /me/forwarders | Add a forwarder | source, destination |
POST /me/forwarders/{id}/delete | Delete a forwarder | — |
GET /me/databases | List MySQL databases with their size | — |
POST /me/databases | Create a database and a user of the same name with all privileges | name, password |
POST /me/databases/{name}/delete | Delete a database (and its user of the same name) | — |
GET /me/dns/{domain} | List DNS records | — |
POST /me/dns/{domain} | Add a DNS record | name, type, content, ttl, priority |
POST /me/dns/{domain}/{id}/delete | Delete a DNS record | — |
GET /me/cron | List cron jobs | — |
POST /me/cron | Add a cron job | schedule, command |
POST /me/cron/{id}/delete | Delete a cron job | — |
GET /me/ssl | Certificate status per domain (status, issuer, expiry) | — |
All paths start with /api/v1.
Examples
Account and usage:
curl -s -H "Authorization: Bearer $TOKEN" \
https://server1.example.com:2083/api/v1/me
Create a mailbox:
curl -s -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"address":"[email protected]","password":"Mailb0x-Passw0rd!","quota_mb":2048}' \
https://server1.example.com:2083/api/v1/me/email
Create a database and its user:
curl -s -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"blog","password":"Db-Passw0rd!2026"}' \
https://server1.example.com:2083/api/v1/me/databases
# {"status":1,"data":{"database":"shop_blog","user":"shop_blog","host":"localhost"}}
Add a DNS record:
curl -s -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"api","type":"A","content":"203.0.113.10","ttl":3600}' \
https://server1.example.com:2083/api/v1/me/dns/example.com
Add a cron job that runs every 15 minutes:
curl -s -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"schedule":"*/15 * * * *","command":"/usr/bin/php ~/public_html/cron.php"}' \
https://server1.example.com:2083/api/v1/me/cron
Delete a forwarder (use the id from GET /me/forwarders):
curl -s -X POST -H "Authorization: Bearer $TOKEN" \
https://server1.example.com:2083/api/v1/me/forwarders/42/delete
Webhooks
With webhooks, Sahaab tells your system when something happens, so it doesn't have to ask. Use them for a billing system, monitoring, or a chat channel.
Add a webhook
- Admin panel → Account → Webhooks (resellers need the API tokens privilege).
- Enter the Address that receives the messages, for example
https://billing.example.com/hooks/hosting. - Tick the Events to send, then click Add webhook.
- Copy the signing secret. Click Show secret to see it again later.
Click Send test to send a ping message. Recent deliveries shows each message and whether it arrived.
An administrator's webhooks hear about every account. A reseller's webhooks hear only about the reseller's own accounts, and their address must be on the public internet.
Events
| Event | When |
|---|---|
account.created | An account was created (it is ready a moment later) |
account.suspended, account.unsuspended | An account was suspended or unsuspended |
account.terminated | An account was deleted |
account.changed | Package, contact email, main domain or username changed |
account.password | The account password changed |
domain.added, domain.removed | An addon domain, subdomain or alias was added or removed |
domain.moved | An addon domain moved to another account |
email.created, email.deleted | A mailbox was created or deleted |
disk.limit | An account reached 90% or 100% of its disk space (once a month each) |
bandwidth.limit | An account used its monthly bandwidth (once a month) |
task.failed | A server task failed (for example creating an account) |
The message
Each message is a POST with a JSON body:
POST https://billing.example.com/hooks/hosting
Content-Type: application/json
X-Sahaab-Event: account.suspended
X-Sahaab-Delivery: 42
X-Sahaab-Signature: sha256=5d1c…
{"event": "account.suspended", "id": "ce2d9ae57d35fa63", "time": "2026-10-20T10:04:47+00:00",
"server": "server.example.com",
"data": {"account": {"username": "shop1", "domain": "shop.com", "owner": "reseller1"},
"reason": "Unpaid invoice"}}
owner is the reseller's username, or null for the administrator's own accounts.
Check the signature
X-Sahaab-Signature is the HMAC-SHA256 of the raw body with your secret. Compare it before you trust a message:
$body = file_get_contents('php://input');
$expected = 'sha256=' . hash_hmac('sha256', $body, $secret);
if (!hash_equals($expected, $_SERVER['HTTP_X_SAHAAB_SIGNATURE'] ?? '')) {
http_response_code(401);
exit;
}
Answer and retries
Answer with any 2xx status within 10 seconds. If the answer is anything else, or no answer arrives, the message is sent again after 1, 5, 30 and 120 minutes, then given up. The same message can arrive twice, so use its id to ignore duplicates.