StackRegent automation examples with the REST API
Working scripts for the jobs a billing system or an operations team automates first: provisioning, listing, suspending, changing plans, removing and backing up hosting subscriptions.
Before you run these StackRegent automation examples#
Every example uses endpoints from the REST API reference and nothing else. You need:
- an API key (see API keys); an administrator key for the provisioning examples, or a reseller key to provision the reseller's own customers,
- the panel address, written here as
https://panel.example.com:9443, - a trusted certificate on the panel, or
-SkipCertificateCheck(PowerShell 7) and-k(curl) for a first test only.
Creating, changing and removing hosting objects is queued as tasks. Each example waits for its task with the helper below, so the script knows whether the work succeeded before it moves on.
A small PowerShell helper#
Save this as sr.ps1 and dot-source it (. .\sr.ps1) in the scripts that follow. It works in Windows PowerShell 5.1 and PowerShell 7.
[Net.ServicePointManager]::SecurityProtocol = 'Tls12'
$SR = 'https://panel.example.com:9443/api'
$SRHeaders = @{ Authorization = "Bearer $env:SR_API_KEY" }
function Invoke-SR([string]$Method, [string]$Path, $Body = $null) {
$req = @{ Method = $Method; Uri = "$SR$Path"; Headers = $SRHeaders }
if ($null -ne $Body) { $req.Body = ($Body | ConvertTo-Json -Depth 6 -Compress); $req.ContentType = 'application/json' }
Invoke-RestMethod @req
}
function Wait-SRTask([int64]$Id, [int]$TimeoutSeconds = 900) {
$until = (Get-Date).AddSeconds($TimeoutSeconds)
do {
Start-Sleep -Seconds 3
$t = Invoke-SR GET "/tasks/$Id"
} while ($t.status -in @('queued', 'running') -and (Get-Date) -lt $until)
if ($t.status -ne 'success') { throw "Task $Id ended as $($t.status): $($t.log)" }
$t
}Keep the key in an environment variable or a secret store, not in the script. Invoke-RestMethod throws on HTTP errors; the JSON error message is in the exception's details.
Create a customer and a subscription#
One call creates the customer and, if you include a subscription object, the customer's first subscription. First find the ID of the hosting plan:
. .\sr.ps1
(Invoke-SR GET '/plans?kind=hosting').items | Format-Table id, name, subscriptionsThen create both:
. .\sr.ps1
$order = @{
type = 'customer'
login = 'jdoe'
password = 'Long-Unique-Passw0rd-1'
contact_name = 'Jane Doe'
company = 'Example Ltd'
email = 'jane@example.com'
country = 'GB'
external_id = 'INV-10234'
subscription = @{
plan_id = 3
domain = 'example.com'
sys_user = 'examplecom'
sys_password = 'Another-Long-Passw0rd-2'
}
}
$res = Invoke-SR POST '/clients' $order
if ($res.subscription_error) { throw "Customer $($res.id) created, subscription refused: $($res.subscription_error)" }
Wait-SRTask $res.task_id | Out-Null
"Customer $($res.id), subscription $($res.subscription_id) is ready"What to know about the fields:
loginstarts with a letter or digit and may contain letters, digits,.,_,@and-, up to 64 characters. It must not be in use.passwordandsys_passwordmust meet the server's password policy; a weak one is refused with the missing requirements in the message.contact_nameorcompanyis required.countryis a two-letter code.external_idholds your billing system's reference, up to 64 characters.sys_useris the subscription's Windows user: 3 to 20 lower-case letters, digits,-or_, starting with a letter. Leave it out and the panel suggests one from the domain. The password must not contain the user name.- Add
ip_idto choose an IP address (fromGET /api/ips) andaddon_idsfor add-on plans. Withoutip_idthe server's default shared address is used. - The answer is
{"id": ..., "subscription_id": ..., "task_id": ...}. If the customer was created but the subscription was refused, the answer hassubscription_errorandsubscription_fieldsinstead, and the customer exists.
With a reseller key, the same call creates the customer under that reseller: type is ignored, and the plan and IP address must be the reseller's own.
To add a subscription to an existing customer, call POST /api/subscriptions with client_id and the same subscription fields:
$res = Invoke-SR POST '/subscriptions' @{ client_id = 42; plan_id = 3; domain = 'example.org'; sys_password = 'Another-Long-Passw0rd-3' }
Wait-SRTask $res.task_id | Out-NullThe same with curl:
curl -s -X POST "https://panel.example.com:9443/api/subscriptions" \
-H "Authorization: Bearer $SR_API_KEY" -H "Content-Type: application/json" \
-d '{"client_id":42,"plan_id":3,"domain":"example.org","sys_password":"Another-Long-Passw0rd-3"}'
# {"id":57,"task_id":912}
curl -s "https://panel.example.com:9443/api/tasks/912" -H "Authorization: Bearer $SR_API_KEY"List subscriptions#
GET /api/subscriptions returns pages of up to 1000. This collects all of them and prints a table:
. .\sr.ps1
$all = @(); $page = 1
do {
$p = Invoke-SR GET "/subscriptions?page=$page&size=500"
$all += $p.items; $page++
} while ($all.Count -lt $p.total)
$all | Sort-Object name | Format-Table id, name, client_login, plan_name, status, ip, disk_usedFind the subscription of a domain with q, which matches the subscription name, the customer's name and the system user:
(Invoke-SR GET '/subscriptions?q=example.com').items | Select-Object id, name, statusGET /api/subscriptions/{id} adds the subscription's domains, and GET /api/subscriptions?client=42 lists one customer's subscriptions. In bash with jq:
curl -s "https://panel.example.com:9443/api/subscriptions?size=1000" -H "Authorization: Bearer $SR_API_KEY" \
| jq -r '.items[] | [.id, .name, .client_login, .plan_name, .status] | @tsv'Suspend and reactivate a subscription#
A subscription's status is active, suspended or disabled. The change is applied to the subscription's websites and services by a task.
. .\sr.ps1
$sub = (Invoke-SR GET '/subscriptions?q=example.com').items | Where-Object name -eq 'example.com'
$t = Invoke-SR POST "/subscriptions/$($sub.id)/status" @{ status = 'suspended' }
Wait-SRTask $t.task_id | Out-Null
# when the invoice is paid
$t = Invoke-SR POST "/subscriptions/$($sub.id)/status" @{ status = 'active' }
Wait-SRTask $t.task_id | Out-NullTo suspend everything a customer has, change the customer's status instead; the panel applies it to each of the customer's subscriptions:
Invoke-SR POST '/clients/42/status' @{ status = 'suspended' }Change a subscription's plan#
Upgrades and downgrades move the subscription to another hosting plan, optionally with add-on plans:
$t = Invoke-SR PUT "/subscriptions/57/plan" @{ plan_id = 5; addon_ids = @() }
Wait-SRTask $t.task_id | Out-NullRemove a subscription or a customer#
Removing a subscription deletes its websites, files, databases and mail through a task. Take a backup first if you may need the data.
$t = Invoke-SR DELETE '/subscriptions/57'
Wait-SRTask $t.task_id | Out-Null
# remove the customer and all of their subscriptions
Invoke-SR DELETE '/clients/42'A reseller that still has customers cannot be removed; the call answers 409 until its customers are removed or moved.
Back up a subscription and download the backup#
POST /api/backups queues a backup of the server, a customer (client) or a subscription. content chooses any of config, files, databases and mail (all four if left out), and store is local, remote or both; remote storage must be set up first. A password of at least 8 characters makes the backup restorable on another server.
. .\sr.ps1
$req = @{
scope = 'subscription'
object_id = 57
content = @('config', 'files', 'databases', 'mail')
store = 'local'
comment = 'before plan change'
password = $env:SR_BACKUP_PASSWORD
}
$b = Invoke-SR POST '/backups' $req
Wait-SRTask $b.task -TimeoutSeconds 7200 | Out-Null
$list = Invoke-SR GET '/backups?scope=subscription&id=57'
$list.backups | Where-Object id -eq $b.id | Format-List id, file, size, status, message
Invoke-WebRequest -Uri "$SR/backups/$($b.id)/download" -Headers $SRHeaders -OutFile "D:\backups\subscription-57.zip"The answer of POST /api/backups names the task task, not task_id. A backup's status is creating, ready or failed, and a download is possible only when it is ready. A nightly job can loop over the subscriptions from the listing example and back up each one in turn; backing up one at a time keeps the load on the server even.
For backups on a schedule, you do not need a script: set the schedule and how many backups to keep in the Backup Manager, or with PUT /api/backups/schedule. Restores pick subscriptions and content types (files, databases, mail), not single files; see the backup guide.
Check that the panel is up#
GET /api/health needs no key and answers {"ok": true, "time": "..."}. An authenticated call also confirms that the key works and the license is in order:
curl -fsS https://panel.example.com:9443/api/health
curl -fsS -o /dev/null -w '%{http_code}\n' https://panel.example.com:9443/api/services -H "Authorization: Bearer $SR_API_KEY"Run the same calls on the server#
On the server, stackregent api runs any of these calls as the main administrator without a key:
stackregent api GET "/api/subscriptions?q=example.com"
stackregent api POST /api/subscriptions/57/status @suspend.jsonSee command line.
Frequently asked questions
How do I create a hosting account in StackRegent through the API?
Call POST /api/clients with the customer's details and a subscription object holding plan_id, domain and sys_password, or POST /api/subscriptions for an existing customer. Then wait for the returned task to report success.
How do I suspend a StackRegent subscription from a billing system?
Call POST /api/subscriptions/{id}/status with {"status":"suspended"}, and with {"status":"active"} to reactivate it. Use POST /api/clients/{id}/status to suspend all of a customer's subscriptions at once.
Is there a WHMCS or Blesta module for StackRegent?
There is no ready-made billing-system plugin. Billing systems provision through the REST API with the calls on this page: create, suspend, reactivate, change plan and remove.
How do I find a subscription's ID from its domain?
Call GET /api/subscriptions?q=example.com and pick the item whose name is the domain. Website IDs for a subscription are in the domains list of GET /api/subscriptions/{id}.
Can I back up all subscriptions with a script?
Yes. List the subscriptions, then call POST /api/backups for each with scope subscription and wait for each task. For regular backups, a schedule in the Backup Manager does the same without a script.
Why does creating a customer return subscription_error?
The customer was created, but the subscription was refused, for example because the domain is taken, the system user exists or the password is too weak. Fix the field named in subscription_fields and call POST /api/subscriptions with the customer's id.