Authentication & API keys
Programs authenticate to the PlusPanel API with an API key sent as a bearer token. The panel itself uses a browser session; the CLI can get a key for you through a browser sign-in (device flow).
Sending a key
Section titled “Sending a key”GET /api/v1/auth/me HTTP/1.1Host: panel.example.comAuthorization: Bearer ppk_k3nmw2x7q4tz_8lV0m1Q2…- A key has the form
ppk_<key id>_<secret>. The key id (12 characters) is public: it appears in key lists and in the audit log. The secret is shown once, when the key is created or rotated. PlusPanel stores only a hash of the key and cannot show it again. - A request with an
Authorizationheader ignores cookies entirely: no session, no CSRF token needed. - A malformed, unknown, expired or revoked key answers
401with the codeinvalid_api_keyandWWW-Authenticate: Bearer error="invalid_token". GET /api/v1/auth/metells you who the key acts as, and with which roles and permissions.
Scopes
Section titled “Scopes”Scopes come from the same roles and permissions as the panel; there is no second permission system. Each permission belongs to a resource and is either a read or a write:
| Scope | Allows |
|---|---|
<resource>:read |
Reading that resource, for example servers:read, accounts:read |
<resource>:write |
Changing it (and reading it: write implies read), e.g. sites:write |
*:read |
Reading every resource |
* |
Everything the user may do (there is no *:write: write *) |
The resources are accounts, alerts, apikeys, audit, backups, databases, dns, jobs, mail, observability, plans,
runtime, security, servers, sftp, sites, storage, tenant, updates, users, waitlist and
webhooks; GET /api/v1/api-keys/scopes lists them with the permissions each scope
covers, and the panel shows the same list when you create a key. On GET requests a resource’s read
scope also covers the reads that the panel guards with a management permission (listing sites, for
example), except a few strict ones that return a secret or the data itself: a job’s one-time output,
file contents and the web terminal (accounts), invitation links (users), restores and backup
downloads (backups) and lab test probes (jobs) need the write scope of their resource.
A key never does more than its user. A request made with a key is allowed only if both are true:
- the user’s role (and, for resellers and clients, the accounts they reach) allows it, exactly as in the panel, and
- one of the key’s scopes covers the permission.
If the user loses a role, their keys lose it too. A few self-service actions are never available with
a key, whatever its scopes: changing a password or a second factor, managing sessions, step-up,
impersonation and approving a CLI sign-in. GET /auth/me, GET /tenant/edition and GET /api-keys/scopes work with any key.
A key that is missing a scope answers 403 insufficient_scope (the message names the scope it needs); an
endpoint that needs a signed-in session answers 403 api_key_not_allowed.
Personal and organization keys
Section titled “Personal and organization keys”- A personal key acts as you. It stops working when your user is disabled or removed.
- An organization key acts as a dedicated service user of the organization (role admin, no password,
unable to sign in). It survives the people who created it and is managed by the organization’s
admins (creating one needs the organization-wide
apikeys.managepermission). Use it for integrations that must keep working when staff changes. An organization key can create organization keys only.
Keys created by the CLI’s browser sign-in are personal keys (source device, valid 90 days by default;
see below).
Expiry, rotation, revocation
Section titled “Expiry, rotation, revocation”- Expiry is optional and set when the key is created (it cannot be changed later). An expired key answers
401 invalid_api_key. - Rotation (
POST /api-keys/{id}/rotate) gives the key a new secret and keeps its id, scopes and settings. Withgrace_seconds(at most 86400) the old secret stays valid for a grace period while you deploy the new one. A personal key is rotated only by its owner. - Revocation is immediate and final.
- The panel shows each key’s last use (time and IP address) and its daily request counts.
IP allowlist
Section titled “IP allowlist”A key can be limited to a list of networks (CIDRs or single addresses, IPv4 and IPv6; a CIDR with host
bits set, like 192.0.2.7/24, is refused). A request from any other address answers
403 api_key_ip_denied, even with a valid key.
Keys that manage keys
Section titled “Keys that manage keys”A key can create or change keys only with the apikeys:write scope, and only with scopes, expiry,
allowlist and rate limit no wider than its own (else 403 api_key_wider_than_caller). Every creation, rotation, revocation, change and refusal is
recorded in the audit log.
Device flow
Section titled “Device flow”The CLI’s pluspanel login gets its key through a browser sign-in shaped like
RFC 8628; other tools can use the same two public endpoints
(no credential, no Idempotency-Key):
POST /api/v1/auth/device/codewith{"client_name": "…"}answers a secretdevice_code, auser_code(XXXX-XXXX), the panel’s/devicepage asverification_uri(andverification_uri_completewith the code),expires_in(600 seconds) andinterval(5 seconds). At most 10 codes a minute and 5 pending requests per address (429 rate_limited).- Show the
user_codeand open the page. The person signs in as usual, checks the request (name, address, time) and approves it (confirming their identity again if they have not in the last 5 minutes) or denies it. - Poll
POST /api/v1/auth/device/tokenwith{"device_code": "…"}everyintervalseconds. Until the decision it answers400with the codeauthorization_pending;slow_downmeans you polled too fast (add 5 seconds to the interval);access_deniedandexpired_token(after 10 minutes, for an unknown code, or once the key was handed out) end the flow. These are problem documents: read the RFC 8628 error fromcode. - The first poll after the approval answers
200withtoken(shown once),key,tenantanduser: a personal key namedCLI: <client_name>, with full or read-only access as approved.
curl -fsS -X POST "$PLUSPANEL_URL/api/v1/auth/device/code" \ -H "Content-Type: application/json" -d '{"client_name": "deploy tool on laptop"}'# {"device_code":"…","user_code":"BCDF-GHJK","verification_uri":"https://panel.example.com/device",# "verification_uri_complete":"https://panel.example.com/device?code=BCDF-GHJK","expires_in":600,"interval":5}
curl -sS -X POST "$PLUSPANEL_URL/api/v1/auth/device/token" \ -H "Content-Type: application/json" -d '{"device_code": "…"}'# before the decision: 400 {"code":"authorization_pending", …}# after approval: 200 {"token":"ppk_…","key":{…},"tenant":{"slug":…,"name":…},"user":{…}}Rate limits
Section titled “Rate limits”Each key has a request budget per minute (600 by default, in every edition; a key can be given a lower limit). See Conventions → Rate limits.
Good practice
Section titled “Good practice”- One key per integration, with the narrowest scopes it needs and an expiry date.
- Keep keys in a secret store or an environment variable, never in source code.
- Prefer an organization key for long-running integrations, and an IP allowlist for servers with fixed addresses.
- Rotate on a schedule, and immediately if a key may have leaked; revoke keys you no longer use.