If you're still clicking through WHM to create cPanel accounts one by one, you're leaving time and consistency on the table. Whether you're onboarding a batch of new clients, migrating from a billing platform, or building your own provisioning pipeline, scripting account creation through the WHM API is a foundational automation win.
This tutorial walks through the entire process: authenticating with the WHM API, creating accounts via createacct, handling common errors, and chaining post-provisioning tasks like DNS zone setup and package assignment. Everything here runs on a standard cPanel/WHM server with root access.
Prerequisites#
Before you start, make sure you have:
- Root or reseller-level WHM access on the target server
- A valid API token (preferred) or root password
curlandjqinstalled on your local machine or provisioning server- Basic familiarity with Bash
Generate a WHM API Token#
API tokens are more secure than passing your root password in every request. Create one in WHM:
- Navigate to Home → Development → Manage API Tokens
- Click Generate Token, give it a descriptive name like
provisioning-script - Copy the token immediately — WHM won't show it again
Store it somewhere safe, like a password manager or an environment variable on your automation server.
The Basic API Call#
WHM's API is accessible over HTTPS on port 2087. Here's the minimal structure for creating an account:
#!/bin/bash
WHM_HOST="your-server.example.com"
API_TOKEN="your_api_token_here"
curl -sk \
-H "Authorization: whm root:${API_TOKEN}" \
"https://${WHM_HOST}:2087/json-api/createacct?username=newuser&domain=newuser.example.com&plan=default&[email protected]"The -sk flag skips certificate verification — fine for internal scripts, but in production you should pin or validate your server's certificate.
Key Parameters for createacct#
| Parameter | Required | Description |
|---|---|---|
username | Yes | cPanel username, 8 chars max, alphanumeric |
domain | Yes | Primary domain for the account |
plan | Yes | WHM package name |
contactemail | Recommended | Account owner's email |
password | Auto-generated if omitted | Account password |
ip | No | Dedicated IP (defaults to shared) |
cgi | No | 1 to enable CGI (default), 0 to disable |
Parsing the Response#
WHM returns JSON. The critical field is metadata.result — 1 means success, 0 means failure. Always check it.
response=$(curl -sk \
-H "Authorization: whm root:${API_TOKEN}" \
"https://${WHM_HOST}:2087/json-api/createacct?username=${USERNAME}&domain=${DOMAIN}&plan=${PLAN}&contactemail=${EMAIL}")
result=$(echo "$response" | jq -r '.metadata.result')
reason=$(echo "$response" | jq -r '.metadata.reason')
if [ "$result" -eq 1 ]; then
echo "Account created successfully for ${DOMAIN}"
else
echo "Failed: ${reason}"
exit 1
fiCommon failure reasons include duplicate usernames, domain already existing on the server, or exceeding the reseller's account limit. The reason field gives you a human-readable explanation.
A Complete Provisioning Script#
Here's a more production-ready version that accepts arguments and handles post-creation tasks:
#!/bin/bash
set -euo pipefail
WHM_HOST="${WHM_HOST:?Set WHM_HOST}"
API_TOKEN="${WHM_API_TOKEN:?Set WHM_API_TOKEN}"
USERNAME="$1"
DOMAIN="$2"
PLAN="${3:-default}"
EMAIL="$4"
API_BASE="https://${WHM_HOST}:2087/json-api"
AUTH_HEADER="Authorization: whm root:${API_TOKEN}"
# Validate username length
if [ ${#USERNAME} -gt 8 ]; then
echo "Error: Username must be 8 characters or fewer."
exit 1
fi
# Create the account
response=$(curl -sk -H "$AUTH_HEADER" \
"${API_BASE}/createacct?username=${USERNAME}&domain=${DOMAIN}&plan=${PLAN}&contactemail=${EMAIL}")
result=$(echo "$response" | jq -r '.metadata.result')
reason=$(echo "$response" | jq -r '.metadata.reason // empty')
if [ "$result" != "1" ]; then
echo "Provisioning failed: ${reason}"
exit 1
fi
# Post-provisioning: set up DKIM and SPF
curl -sk -H "$AUTH_HEADER" \
"${API_BASE}/cpanel?cpanel_jsonapi_user=${USERNAME}&cpanel_jsonapi_module=Email&cpanel_jsonapi_func=add_dkim&cpanel_jsonapi_apiversion=3" > /dev/null
curl -sk -H "$AUTH_HEADER" \
"${API_BASE}/cpanel?cpanel_jsonapi_user=${USERNAME}&cpanel_jsonapi_module=Email&cpanel_jsonapi_func=add_spf&cpanel_jsonapi_apiversion=3" > /dev/null
echo "Account ${USERNAME} (${DOMAIN}) provisioned with DKIM and SPF."Save this as provision.sh, make it executable, and call it:
./provision.sh johndoe johndoe.com business [email protected]Batching Accounts from a CSV#
If you're migrating clients or onboarding in bulk, pipe a CSV through a loop:
username,domain,plan,email
alice,alice-store.com,business,[email protected]
bob,bobdesigns.io,starter,[email protected]
charlie,charlie.dev,business,[email protected]tail -n +2 accounts.csv | while IFS=',' read -r username domain plan email; do
./provision.sh "$username" "$domain" "$plan" "$email"
sleep 2 # avoid hammering the API
echo "---"
doneThe sleep 2 is intentional — WHM can behave unpredictably under rapid-fire account creation, especially with DNS zone generation.
Error Handling and Logging#
For anything beyond one-off use, log every provisioning attempt:
LOG_FILE="/var/log/provisioning.log"
echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) | ${USERNAME} | ${DOMAIN} | result=${result} | ${reason}" >> "$LOG_FILE"If you're running this on a Salieno Core instance, the same API patterns apply — WHM-compatible endpoints mean your provisioning scripts don't need to change when you move between cPanel-based setups.
What to Do Next#
Once account provisioning is automated, the natural next steps are:
- Hook it into your billing system — trigger provisioning on payment confirmation via webhooks
- Add suspension/unsuspension scripts using the
suspendacctandunsuspendacctAPI calls - Monitor account limits with
resellerstatsto avoid hitting your allocation - Automate DNS glue records if you're using private nameservers
Manual provisioning doesn't scale. A 30-line Bash script that you've tested once will be more reliable than the 300th time you've clicked through WHM by hand. Start with the single-account call, validate it works, then layer on batching and logging. The WHM API is well-documented and stable — there's no reason to provision accounts manually in 2025.
0 comments
Loading comments…