Authentication
Every API request authenticates with a Personal Access Token (PAT) sent as an HTTP bearer token:
curl https://api.vps-server.host/v1/account \
-H "Authorization: Bearer pat_live_aBc12defGHIjklmnopqrstuvwxyz0123456789ABCD"
Creating a token
PATs are minted from your VPS-Server.Host client area:
- Log in at https://vps-server.host/clientarea.php
- In the left sidebar under Developers, open API Tokens (or jump straight to /clientarea_api_tokens.php)
- In the Create a new token form:
- Token name — a label so you remember what this token is for (
ci-deploy-runner,prod-monitor, etc.) - Scopes — tick at least one. Pick the smallest set that lets the token do its job (scope reference below).
- IP allow-list (optional but strongly recommended) — one or more
CIDRs. Requests from anywhere else return
403 permission_denied. - Expires at (optional) — set a date if the token is short-lived (rotation, contractor access, etc.).
- Confirm your password — your client-area password. This is our step-up check — without it the token won't mint.
- Token name — a label so you remember what this token is for (
- Click Create token.
- The freshly minted secret appears in a yellow banner at the top of the page.
You see the secret exactly once
Copy the pat_live_… value immediately into your secrets manager.
We store only sha256(pepper || token) — there is no way for
us, or for you, to recover the value later. If you lose it, revoke the
token and mint a new one.
The secret format is pat_live_ followed by a high-entropy base62 string.
GitHub-style secret scanning watches for the pat_live_ prefix and we
auto-revoke any token that turns up in a public repository.
Managing existing tokens
The same page lists all tokens on your account. Each row shows:
- Name, prefix (the first 14 characters of the secret — safe to display anywhere), scopes, status (Active / Revoked / Expired)
- Created, last used, last used IP
The Manage ▾ dropdown on the right of each row exposes:
| Action | What it does | Step-up required |
|---|---|---|
| Rotate | Keeps the same token id but issues a new secret. The old secret stops working immediately. | yes |
| Revoke | Soft-disables the token. Requests return 401 authentication_failed. The DB row stays for audit. | yes |
| Delete | Hard-removes the row entirely. Use only after a revoke + grace period. | yes |
All three actions require re-entering your client-area password (step-up).
Rotation is cheap — do it often
A 60-second grace window means the old secret keeps working for a minute after rotation, so a deploy pipeline can pick up the new value without downtime. Rotate any PAT older than 90 days.
Sandbox tokens
Sandbox tokens are prefixed pat_test_ and only authenticate against
https://sandbox.api.vps-server.host. Same minting flow, separate
environment — see Sandbox & test mode.
Sub-account (contact) tokens
If you're logged in as a contact (a sub-user that the primary account-holder added under Contacts → Sub-accounts in their client area), you can mint your own PATs — but only with scopes that match the permissions the primary granted you.
Mapping from a contact's portal permissions to PAT scope groups:
| Contact permission | Scope groups granted |
|---|---|
products | servers:*, snapshots:*, services:* |
manageproducts | same as above (server side-effects) |
domains | domains:*, dns:* |
managedomains | same as above (writes) |
invoices | billing:* |
paymentmethods | payment_methods:* |
tickets | tickets:* |
affiliates | affiliates:read |
contacts | contacts:* |
The form on the token page only shows scopes you're actually allowed to mint — if a scope is missing, ask the primary account-holder to widen your permissions.
Scopes
A PAT carries a scope set that controls what it can do. Scopes are
hierarchical — servers:* matches every servers: action; *:read
matches every read; admin:all is the implicit superuser.
Common scopes:
| Group | Read | Write |
|---|---|---|
| Servers | servers:read | servers:power, servers:rebuild, servers:console, servers:credentials |
| Snapshots | snapshots:read | snapshots:create, snapshots:restore |
| DNS | dns:read | dns:write |
| Account | account:read | account:write |
| Payment methods | payment_methods:read | payment_methods:write |
| Billing | billing:read | billing:pay_invoice, billing:add_funds, billing:accept_quote |
| Services | services:read | services:write, services:cancel |
| Domains | domains:read | domains:write, domains:transfer |
| Tickets | tickets:read | tickets:write |
The full enumerated list lives in the Scope schema.
Least privilege
Default any new token to read-only (*:read). Add write scopes only as
needed. A token used by your CI to reboot servers doesn't need to see
invoices.
IP allow-list
Every PAT carries an ip_allowlist (CIDR list). Empty means any IP. When
set, requests from outside the list return 403 permission_denied.
Configure it at token-creation time. Strongly recommended for any token used outside your laptop:
- CI runners — your CI provider's egress IP block
- Production app servers — their own public IPs
- Office automation — the office WAN IP
Step-up authentication
Destructive or billed actions (:cancel, :pay, :accept, :rebuild,
:restore, :transfer, password changes, PAT rotation/revocation)
require a short-lived X-Confirm-Token on top of your PAT. Get one via:
curl https://api.vps-server.host/v1/auth/step-up \
-H "Authorization: Bearer $PAT" \
-d '{"method":"password","password":"…"}'
The token is valid for 5 minutes. See Step-up authentication for the full flow.
Anything to enable in the client area?
No. The PAT page works for every logged-in client out of the box — no admin toggle, no plan upgrade, no API access flag. The first time you visit the page, the storage table is created automatically.
Internal admin API credentials (used by our staff for administrative tasks) are separate from the public API documented here — you don't need them.
Quick verification
After minting a token with account:read, paste this into your terminal:
PAT='pat_live_…' # the value you just copied
curl -sS https://api.vps-server.host/v1/account \
-H "Authorization: Bearer $PAT" | jq
You should see your account profile:
{
"id": "cli_42",
"first_name": "…",
"last_name": "…",
"currency": "USD",
"credit_balance": { "amount": 1250, "currency": "USD" },
"two_factor_enabled": true,
"created_at": "2024-02-10T15:30:00Z"
}
If you get 401 authentication_failed, the token didn't make it through —
re-check the Authorization header. If you get 403 permission_denied,
the token lacks the account:read scope; revoke it and mint a new one
with the right scope.