Authentication
Authentication
Every WisPanel API request authenticates with an API key sent as a Bearer token. There is no other public auth method β username / password only exists for the interactive login screen.
Panel base: https://<your-server>:3082/api/v1
Step 1 β Issue an API key
POST /api/v1/users/{username}/api-keys
Who may issue: an admin can issue for any user; a user can issue for themselves (reseller = for themselves). The panel does not currently let a reseller mint keys for their sub-users β only self or admin.
POST /api/v1/users/admin/api-keys
Authorization: Bearer <an-existing-api-key>
Content-Type: application/json
{
"name": "WHMCS Provisioning",
"permissions": ["domains", "read"],
"ip_whitelist": [],
"expires_in": 0
}
| Field | Type | Required | Note |
|---|---|---|---|
name |
string | β | label shown in the key list |
permissions |
string[] | β | scope (see below). Default ["read"] |
ip_whitelist |
string[] | β | IPs/CIDRs; empty = any source |
expires_in |
int | β | days; 0 = never |
Response 201 (verified live β the secret is returned once):
{
"success": true,
"message": "API key created. Save the secret key now β it won't be shown again!",
"secret_key": "wsp_56be4cb59e73761bed78686b6c0b37f3bebd929439c20d7d",
"api_key": "wsp_56be4cb59e73761bed78686b6c0b37f3bebd929439c20d7d",
"key": {
"id": "e24ff6f1754c2d9a",
"name": "WHMCS Provisioning",
"key_prefix": "wsp_56be4cb5",
"permissions": ["domains", "read"],
"ip_whitelist": [],
"last_used": "0001-01-01T00:00:00Z",
"created_at": "2026-05-18T17:39:25+07:00",
"expires_at": "0001-01-01T00:00:00Z",
"enabled": true
}
}
Store
secret_keynow β later reads only returnkey_prefix.
Step 2 β Use the key
GET /api/v1/auth/me
Authorization: Bearer wsp_56be4cb59e73761bed78686b6c0b37f3bebd929439c20d7d
Response 200 (verified live):
{ "username": "admin", "email": "[email protected]", "role": "admin",
"status": "active", "creator": "", "package": "default",
"max_domains": -1, "max_databases": -1,
"max_email_accounts": -1, "max_ftp_accounts": -1 }
Permission scope (ENFORCED)
A key's permissions are an enforced narrowing scope on top of the
owning user's role. A key can never exceed the user's role (admin
endpoints still require an admin user); permissions only restrict
further. Vocabulary is flat (not resource.read/resource.write):
| Permission | Grants |
|---|---|
all |
full access (still bounded by the user's role) |
read |
read-only (GET/HEAD) on any resource |
domains |
full access to /domains, /subdomains |
dns |
full access to /dns |
ssl |
full access to /ssl |
database |
full access to /databases |
email |
full access to /email |
files |
full access to /files |
ftp |
full access to /ftp |
backup |
full access to /backups, /restore, /backup-schedules |
Resources without a granular token (users, system, security, β¦)
require all for writes; read allows GET on them. Legacy
resource.read / resource.write strings are accepted and normalised
to the base resource (so old keys keep working).
Out-of-scope request β 403 (verified live):
{ "success": false, "code": "FORBIDDEN",
"error": "API key is not scoped for 'databases' on this request",
"message": "API key is not scoped for 'databases' on this request",
"status": 403 }
Example: a key with ["domains"] can call /domains but
GET /databases β 403; a key with ["read"] can GET anything but any
write β 403.
Disabling API access per account (can_use_api)
API access is a per-account capability (default enabled, inherited from the package). An admin can turn it off via the user's features or the package:
PUT /api/v1/users/{username}/features
{ "api": false, "php": true, "ssl": true, ... }
While disabled, both creating and using keys for that account are rejected (verified live):
{ "success": false, "code": "FORBIDDEN",
"error": "API access is disabled for this account",
"message": "API access is disabled for this account", "status": 403 }
Re-enable with "api": true. Package field: can_use_api (api in
package data).
Reseller delegation β can_grant_api
Whether a reseller may grant API access to the users it owns is a
separate, admin-controlled lever: can_grant_api on the reseller
(reseller-package field api; default enabled).
An admin sets it directly:
PUT /api/v1/resellers/{username}
{ "can_grant_api": false }
While can_grant_api is false, the reseller turning api on for
one of its users is rejected (verified live):
{ "success": false, "code": "FORBIDDEN",
"error": "Permission denied: cannot grant API (not allowed by your reseller package)",
"status": 403 }
Turning api off is always allowed β the gate only blocks
granting. admin is unaffected by this lever.
Manage / interactive login
GET /users/{username}/api-keys (secret redacted),
PUT /users/{username}/api-keys/{id},
POST /users/{username}/api-keys/{id}/toggle,
DELETE /users/{username}/api-keys/{id} β
{ "success": true, "message": "API key deleted" }.
POST /api/v1/auth/login (login screen only). Verified live:
400 {"code":"VALIDATION_ERROR","error":"Username and password are required"};
401 {"code":"UNAUTHORIZED","error":"Invalid credentials"}.
Logging in customers from billing β Single Sign-On (SSO). Error envelope catalogue β Error Handling.