HF PanelDocsv1.0.0

API overview

The token API's address, API tokens, answer format, status codes, errors and the sign-in lock-out.

The token API lets a program manage HF Panel without a browser: hosting accounts, packages and resellers, domains, DNS, email, databases, SSL, backups, cron jobs, FTP accounts and the server itself. Billing systems, your own scripts and the HF Server desktop app use it. This page covers the rules shared by every endpoint. The endpoints themselves are in the API reference.

The address

Address When to use it
https://api.yourdomain.com:2087/v1 the API's own address; use it for new code
https://api.yourdomain.com/v1 and https://api.yourdomain.com:8443/v1 the same API on ports 443 and 8443, for clients that cannot use port 2087
https://panel.yourdomain.com:8443/api/v1 the old address; it keeps working and gives the same answers

The paths after the base are the same everywhere: GET https://api.yourdomain.com:2087/v1/accounts is the same call as GET https://panel.yourdomain.com:8443/api/v1/accounts. HF Panel never redirects from the old address to the new one, because a redirect would drop the body of a POST and the Authorization header.

Note: a server has api.yourdomain.com only when the separate panel addresses are switched on. Without it, use the old address. GET /api/v1 on the old address tells you the new address in base when there is one.

Programs only

api.yourdomain.com answers only programs. A request that looks like a person's browser opening a page (it asks for HTML, or sends Sec-Fetch-Mode: navigate) and has no Authorization header gets 403 Forbidden, on every port. There are no panel pages, no session cookies and no CORS headers on this address.

  • GET https://api.yourdomain.com/ answers {"name": "HF Panel API", "version": "v1", "docs": "https://api.yourdomain.com:2087/v1"}. The name follows your brand (white label).
  • GET /v1 lists the endpoints and needs no token.
  • GET /v1/openapi.json is the OpenAPI 3.1 description and needs no token.
  • Any other address answers 404 with {"ok": false, "error": "Not found."}.
bash
curl -s https://api.yourdomain.com:2087/v1
json
{
    "ok": true,
    "data": {
        "name": "HF Panel API",
        "version": "v1",
        "auth": "Authorization: Bearer <token>",
        "base": "https://api.yourdomain.com:2087/v1",
        "old_base": "https://panel.yourdomain.com:8443/api/v1",
        "openapi": "https://api.yourdomain.com:2087/v1/openapi.json",
        "endpoints": [
            "GET /v1/me",
            "GET /v1/packages",
            "GET /v1/accounts",
            "…"
        ]
    }
}

Authentication: API tokens

Every request, except GET /v1 itself, sends an API token in the Authorization header:

http
Authorization: Bearer hf_xxx

A token is hf_ followed by 48 lower-case hexadecimal characters. No cookie and no CSRF token are needed: the token is the proof. HF Panel keeps only a SHA-256 hash of each token, so it cannot show a token again after it is made.

Make a token

Where: your name at the top right › Password & security › API tokens (in Server Admin, Reseller Panel or Control Panel)

  1. In Name, type what the token is for, for example Billing system.
  2. Choose the Access:
    • Read only: the token can only read.
    • Read & change: the token can also create, change and delete.
  3. Optionally, fill in Allowed IPs: IP addresses or ranges, separated by spaces or commas, for example 203.0.113.5 198.51.100.0/24. Leave it empty to allow any address.
  4. Type your password (or a code from your authenticator app when two-factor login is on), then click Create token.
  5. Copy the token straight away with Copy token. It is not shown again.

The API tokens table shows each token's name, its first characters, its access, its allowed IPs and when and from where it was last used. Click Delete to stop a token at once. Anything that uses it stops working.

Rule What it means
Up to 20 tokens per login delete one before you make another
A token acts as the person who made it an administrator's token sees every hosting account, a reseller's token only their customers, a customer's token only their own account
Read-only staff logins can make only Read only tokens; their tokens never change anything
Suspended logins their tokens stop working (403 "This login is suspended.")
Reseller privileges a reseller's tokens can only do what the administrator allows that reseller under Reseller privileges
Not through "Log in as" tokens are made and deleted only by the person themselves

Important: treat a Read & change token like a password. Limit it to your billing server's IP address whenever you can.

Tokens for a team role

A customer can also make tokens limited to one team role.

Where: Control Panel › Account › Team › API tokens for a role

Only the account owner can make these, from their own login and with their password. Pick a Role (Full access, Websites & files, Email only, Databases only, Billing only or Read-only) and an Access, then click Make token. These tokens count towards the same limit of 20.

Endpoint Allowed for
/v1/me, /v1/status every role
/v1/domains, /v1/sync/sites, /v1/tunnels roles with websites: Full access, Websites & files, Read-only
/v1/mailboxes roles with email: Full access, Email only, Read-only
/v1/databases, /v1/mongo roles with databases: Full access, Databases only, Read-only
/v1/accounts/{username}/domains, …/dns, …/ssl, …/backups, …/cron, …/ftp roles with websites
/v1/accounts/{username}/email roles with email
/v1/accounts/{username}/mysql, …/postgres roles with databases

A Read-only role token can only use GET. Anything else answers 403 "This token’s team role doesn’t include this."

IP allow-list

When a token has Allowed IPs, a request from any other address answers 403 "This token cannot be used from

." Each entry is a single address (IPv4 or IPv6) or a range in CIDR form (`198.51.100.0/24`, `2001:db8::/32`).

Requests

  • Send bodies of POST, PUT and PATCH requests as JSON with Content-Type: application/json. A normal form post (application/x-www-form-urlencoded) works too.
  • In JSON bodies, true counts as "1", and numbers may be sent as numbers or text.
  • The older per-account endpoints (/v1/domains, /v1/mailboxes, /v1/databases, /v1/mongo, /v1/tunnels) take ?account=<username> in the query when you use an administrator's or reseller's token. With a customer's token they always use the token's own account.
  • The newer ones name the account in the path: /v1/accounts/{username}/dns/…. A customer's token may only name its own account. Staff work in the account as Log in as does (see per-account endpoints).
  • A body that is neither JSON nor a form answers 400 Send the body as JSON, e.g. {"username":"param"}.

Answers

Every answer is pretty-printed JSON with Content-Type: application/json; charset=utf-8, and is never cached (Cache-Control: no-store).

json
{
    "ok": true,
    "data": {}
}
json
{
    "ok": false,
    "error": "No such account."
}

error is a plain sentence you can show to a person. When a change was made but something around it was not (for example "Saved, but Stripe was not updated: …"), the answer is still "ok": true and also has a warnings list. Times such as created_at are UTC, written YYYY-MM-DD HH:MM:SS. A value that is not known is null.

Status codes

Status Means
200 done
201 created: the POST endpoints that make something (an account, package, reseller, domain, record, mailbox, database, backup, cron job …)
400 the body is not JSON or a form, or ?account= is missing
401 no token, a malformed token, or an unknown or deleted token
402 the paid edition's licence needs attention; the body is {"error": "The control panel licence needs attention: …"}
403 a Read only token or read-only staff login tried to change something; the endpoint is for administrator and reseller tokens only; the IP address is not allowed; the login is suspended; the feature is not part of the account's plan; the reseller lacks the privilege; the action needs another administrator's approval
404 no such endpoint, account, domain, record and so on. An account your token may not manage is also "No such account."
409 a request with the same Idempotency-Key is still running
422 refused with a message: a bad field, an unknown package, the account limit, an Idempotency-Key already used for another request and so on
429 the token's request limit is used up (see Retry-After), or too many failed attempts from your IP address (15 minutes)
500 the server refused the change; the message starts with Server:

Common error messages

Status error
401 Send an API token: Authorization: Bearer hf_…
401 Unknown or deleted token.
403 This token is read-only. Make a "Read & change" token to do this.
403 This staff login is read-only, so its tokens can only read.
403 Only administrator and reseller tokens can do this.
403 This feature is not part of your hosting plan.
403 Your hosting provider has not given your reseller account this permission.
403 This needs another administrator's approval first. Do it in the control panel, where it is sent for approval: Delete a hosting account.
400 Add ?account=<username> to say which account.
404 No such endpoint: GET /v1/…
429 Too many failed attempts from this IP. Try again in 15 minutes.

Idempotency-Key: safe retries

A billing system that sends a POST and loses the answer (a time-out, a dropped connection) cannot know whether the account was made. Send an Idempotency-Key header with every POST that makes something, and send the same request again with the same key:

bash
curl -s -X POST -H "$H" -H "Idempotency-Key: order-1001" -H "Content-Type: application/json" \
  -d '{"username":"sarahbakes","email":"sarah@example.com","package":"Starter"}' $A/accounts
Rule What it means
The key 1–255 printable characters without spaces; for example your order or invoice number. A bad key answers 400
Remembered for 24 hours per token: the same key with the same request (method, path, query and body) gets the first answer again, with the header Idempotent-Replayed: true, and nothing is done twice
Another request the same key with a different request answers 422 "This Idempotency-Key was already used for another request."
Still running while the first request is still being done, the same key answers 409; try again a moment later
What is kept answers below 500 (refusals too), except 429. A 500 or 429 is not kept, so you can retry it with the same key

The kept answer is encrypted with the server's key, because it may hold a new password. The key works on POST only; GET, PUT, PATCH and DELETE are safe to repeat anyway.

Rate limits and lock-out

Each token may make 600 requests a minute (the server's administrator can change this with api_rate_limit in /etc/hostfog/config.json; 0 switches the limit off). Every answer to a token says where it stands:

Header What it says
X-RateLimit-Limit requests this token may make a minute
X-RateLimit-Remaining requests left in this minute
X-RateLimit-Reset when the minute ends, as Unix time (seconds)

One request more answers 429 "Too many requests with this token: 600 a minute. Try again in N seconds." with a Retry-After header (seconds). Each token has its own minute.

The token API also shares the sign-in page's lock-out:

  • A request with no token, a malformed token, or an unknown or deleted token counts as a failed sign-in for your IP address.
  • After 8 failed attempts in 15 minutes, every request from that address answers 429 "Too many failed attempts from this IP. Try again in 15 minutes." until the 15 minutes have passed, even with a good token. This answer has no Retry-After.
  • IPv4 addresses are counted one by one. IPv6 addresses are counted by their /64 network.

Tip: if a script suddenly gets 429 without Retry-After, check that it sends the full token with the word Bearer and a space in front of it. Fix the script, then wait 15 minutes.

Pagination

The token API does not page its lists. GET /v1/accounts, GET /v1/domains and the other lists return every item the token may see in one answer.

Approvals

When the administrators have set an action to need a second administrator's approval, the token API cannot wait for one. DELETE /v1/accounts/{username}, DELETE /v1/resellers/{username} and a whole restore (POST /v1/accounts/{username}/backups/{backup}/restore) then answer 403 and the action must be done in the panel, where it is sent for approval. The same goes for a reseller whose deletes and restores wait for an administrator. When approvals ask only for your password, the token itself is enough.

The interface's own API

The panels, HF Mail, HF Files and HF Studio are browser apps. They call /api/v1/… on their own address (for example https://panel.yourdomain.com:8443/api/v1/domains) with a session cookie. That interface API is not meant for your scripts, and it has stricter rules:

  • Every call, GET included, must send the session's CSRF token in the X-CSRF-Token header. The token comes from GET /api/v1/_boot (data.csrf) and in the csrf field of every answer; always keep the newest. A wrong or missing token answers 403 with a fresh csrf.
  • Calls from another site are refused (403): the Origin must be the same host and Sec-Fetch-Site must be same-origin or none.

On the panel's address, a request with Authorization: Bearer hf_… is answered by the token API instead, with no cookie and no CSRF token. On api.yourdomain.com only the token API answers: a request that brings only a session cookie gets the token API's 401.

Tip: for scripts, always use an API token. It does not expire with a session and needs no CSRF token.