API Reference

Sahaab has three programming interfaces:

InterfaceForAddressToken made in
Billing-system interfaceBilling systems (Blesta, HostBill and others)https://your-server:2087/json-api/<function>Admin panel → API Tokens
API v1Your own scripts and integrations, as admin or resellerhttps://your-server:2087/api/v1/...Admin panel → API Tokens
Customer APIA hosting customer's own scriptshttps://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/v1 only GET requests are allowed. On /json-api only version, applist, listaccts, accountsummary, listpkgs, showbw and gethostname are 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.

ActionPrivilege needed
Create an accountCreate accounts
Remove / terminate an accountTerminate accounts
Suspend or unsuspendSuspend and unsuspend accounts
Change package, modify accountModify accounts
Change passwordChange account passwords
One-time sign-in linkLog 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": {...}} ` result is 1 on success and 0 on failure. On failure, reason` explains why.
  • Without api.version=1, the answer uses the older format that older modules expect.

Functions

FunctionWhat it doesParameters
createacctCreate an accountusername, domain, plan (package name), contactemail, password
suspendacctSuspend an accountuser, reason (optional)
unsuspendacctUnsuspend an accountuser
removeacctTerminate an accountuser
changepackageChange the packageuser, pkg
passwdChange the passworduser, password
modifyacctChange the contact email, main domain or usernameuser, and any of contactemail, domain, newuser
listacctsList accountsoptional search, searchtype (user, domain, owner, package, ip), searchmethod=exact
accountsummaryOne account's detailsuser
listpkgsList packages and their limits—
showbwBandwidth used per account in a monthoptional month, year, search
create_user_sessionA one-time sign-in link to the customer's control panel (single use, valid 60 seconds)user
gethostnameThe server hostname—
versionThe panel version—
applistThe 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 createacct has no plan, the first package is used. If it has no contactemail, the email of the token's owner is used.
  • createacct returns 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 modifyacct works 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: 401 wrong token, 403 blocked address or missing privilege, 422 invalid input (the error says why).

Endpoints

Method and pathWhat it doesBody
GET /api/v1/accountsList accounts (optional ?search=)—
POST /api/v1/accountsCreate an accountusername, domain, email, password, package (name)
GET /api/v1/accounts/{username}One account—
POST /api/v1/accounts/{username}/suspendSuspendreason (optional)
POST /api/v1/accounts/{username}/unsuspendUnsuspend—
POST /api/v1/accounts/{username}/passwordChange the passwordpassword
POST /api/v1/accounts/{username}/packageChange the packagepackage (name)
POST /api/v1/accounts/{username}/terminateTerminate (cannot be undone)—
POST /api/v1/accounts/{username}/loginOne-time sign-in link to the control panel (60 seconds)—
GET /api/v1/packagesList packages—
GET /api/v1/jobs/{id}Status of a background task—
GET /api/v1/server/statusServer 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 422 with the reason.

Endpoints

Method and pathWhat it doesBody
GET /meAccount, package, disk use and limits—
GET /me/domainsDomains with type and folder—
GET /me/emailMailboxes with quota and size—
POST /me/emailCreate a mailboxaddress, password, quota_mb (default 1024)
POST /me/email/{address}/passwordChange a mailbox passwordpassword
POST /me/email/{address}/deleteDelete a mailbox—
GET /me/forwardersList forwarders—
POST /me/forwardersAdd a forwardersource, destination
POST /me/forwarders/{id}/deleteDelete a forwarder—
GET /me/databasesList MySQL databases with their size—
POST /me/databasesCreate a database and a user of the same name with all privilegesname, password
POST /me/databases/{name}/deleteDelete a database (and its user of the same name)—
GET /me/dns/{domain}List DNS records—
POST /me/dns/{domain}Add a DNS recordname, type, content, ttl, priority
POST /me/dns/{domain}/{id}/deleteDelete a DNS record—
GET /me/cronList cron jobs—
POST /me/cronAdd a cron jobschedule, command
POST /me/cron/{id}/deleteDelete a cron job—
GET /me/sslCertificate 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

  1. Admin panel → Account → Webhooks (resellers need the API tokens privilege).
  2. Enter the Address that receives the messages, for example https://billing.example.com/hooks/hosting.
  3. Tick the Events to send, then click Add webhook.
  4. 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

EventWhen
account.createdAn account was created (it is ready a moment later)
account.suspended, account.unsuspendedAn account was suspended or unsuspended
account.terminatedAn account was deleted
account.changedPackage, contact email, main domain or username changed
account.passwordThe account password changed
domain.added, domain.removedAn addon domain, subdomain or alias was added or removed
domain.movedAn addon domain moved to another account
email.created, email.deletedA mailbox was created or deleted
disk.limitAn account reached 90% or 100% of its disk space (once a month each)
bandwidth.limitAn account used its monthly bandwidth (once a month)
task.failedA 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.