F4 PanelAPIv1← Back to panel

Introduction

F4 Panel provides a REST API to manage hosting from scripts or external systems — create sites, databases, members, login links and more. All endpoints use HTTPS and return JSON.

Base URL

https://host-th-sv1.f4host.com/api/v1

Sandbox Base URL test

https://host-th-sv1.f4host.com/api/sandbox/v1

Use the sandbox base URL to test your integration safely — same tokens, scopes and IP rules apply, but requests make no real changes.

Authentication

Create a token on the API page, then send it with every request in the header:

Authorization: Bearer <your-token>
curl -H "Authorization: Bearer <token>" \
  https://host-th-sv1.f4host.com/api/v1/ping

Sandbox test mode

Test the API without side effects. The sandbox accepts the same tokens, scopes and IP restrictions and validates your request, then returns a simulated response — nothing is created, changed or deleted. Swap the base URL to go live.

curl -X POST -H "Authorization: Bearer <token>"   -H "Content-Type: application/json"   -d '{"domain":"test.com","type":"php"}'   https://host-th-sv1.f4host.com/api/sandbox/v1/sites
# -> {"ok":true,"sandbox":true,"method":"POST","endpoint":"/sites", ... }

Scopes

A token reaches exactly what it was granted. One scope is one action on one group — sites:read can read a site and cannot create or delete one. Calling outside them returns HTTP 403, and nothing is changed.

*Full access, including endpoints added later
<group>:*Every scope in that group, e.g. sites:* — including ones added to it later

🌐 Websites sites:*6 scopes

sites:read List sites, read details and bandwidth
GET /sites
GET /sites/:domain
GET /sites/:domain/bandwidth
sites:write Create new websites
POST /sites
sites:delete destructive Delete websites (including ?purge=1, which drops files and databases)
DELETE /sites/:domain
sites:php Switch a site PHP version
POST /sites/:domain/php
sites:wordpress Install WordPress on a site
POST /sites/:domain/wordpress
sites:suspend Suspend / unsuspend a site
POST /sites/:domain/suspend · /unsuspend

📁 Files files:*2 scopes

files:write Upload files into a site and extract .zip archives
POST /sites/:domain/files
files:link Mint a one-time link into the file manager
POST /sites/:domain/filemanager

🗄 Databases db:*4 scopes

db:read List a site databases (never passwords)
GET /sites/:domain/db
db:write Create a database for a site
POST /sites/:domain/db
db:import destructive Import a dump over a database
POST /sites/:domain/db/import
db:link Mint a one-time link into phpMyAdmin
POST /sites/:domain/phpmyadmin

👥 Members members:*7 scopes

members:read List members, read their details and usage
GET /members
GET /members/:id
GET /members/:id/usage
members:write Create member accounts
POST /members
members:password destructive Set a member panel password
POST /members/:id/password
members:plan Move a member to another plan
POST /members/:id/plan
members:suspend Suspend / unsuspend a member account
POST /members/:id/suspend · /unsuspend
members:ssh destructive Grant or revoke a member shell access
POST /members/:id/ssh
members:delete destructive Delete a member (including ?purge=1, which deletes all their sites)
DELETE /members/:id

🔑 Login links & provisioning access:*2 scopes

sso destructive Mint a one-time login link as a user (120s TTL)
POST /sso
provision Provision in one call: member + site + WordPress + DNS zone + login link
POST /provision

📦 Plans plans:*3 scopes

plans:read List the plans you may sell
GET /plans
plans:write Create and edit plans
POST /plans
POST /plans/:id
plans:delete destructive Delete a plan
DELETE /plans/:id

📊 Server & cron monitor:*2 scopes

server:read Read server statistics
GET /server/stats
cron:read Read the cron job list
GET /cron

🕰 The original scope names (still valid)

Tokens issued before granular scopes existed carry the five original names. Every one of them still behaves exactly as it did: nothing needs reissuing and no calling code needs changing. Each name covers what it always covered.

sitesEverything under Websites, Files and Databases
= sites:read · sites:write · sites:delete · sites:php · sites:wordpress · sites:suspend · files:write · files:link · db:read · db:write · db:import · db:link
membersEverything under Members, plus login links and provisioning
= members:read · members:write · members:password · members:plan · members:suspend · members:ssh · members:delete · sso · provision
plansEverything under Plans
= plans:read · plans:write · plans:delete
serverServer statistics
= server:read
cronThe cron job list
= cron:read

Note: POST /provision used to be reachable with any valid token, because no rule covered it. Existing tokens keep that reach; tokens created or re-saved through the granular picker must be granted provision explicitly.

IP & Security

Restrict each token to specific IPs or CIDR ranges — requests from other IPs get HTTP 403. Keep tokens secret like a password and always use HTTPS.

Response format

Every response is JSON:

{"ok": true,  ...data... }
{"ok": false, "error": "message" }

Missing/invalid token → 401 · out of scope/IP → 403 · not found → 404

Sites sites:* · files:*

GET/sitessites:readList your sites
GET/sites/:domainsites:readSite detail + DB info
POST/sitessites:writeCreate site {domain,type,db,aliases}
DELETE/sites/:domainsites:deleteDelete site (add ?purge=1 to also drop files, system user and databases)
POST/sites/:domain/phpsites:phpSwitch PHP {version}
POST/sites/:domain/wordpresssites:wordpressInstall WordPress
GET/sites/:domain/bandwidthsites:readBandwidth this month
POST/sites/:domain/suspendsites:suspendSuspend / unsuspend
POST/sites/:domain/filemanagerfiles:linkOne-time link straight into the file manager {path?}
POST/sites/:domain/phpmyadmindb:linkOne-time link straight into phpMyAdmin {db?}
POST/sites/:domain/filesfiles:writeUpload a file (multipart) — a .zip is extracted in place {file,path?,extract?,keep_zip?}

Example: upload a zip and extract it into public

curl -X POST -H "Authorization: Bearer <token>" \
  -F "[email protected]" -F "path=public" \
  https://host-th-sv1.f4host.com/api/v1/sites/example.com/files
# → {"ok":true,"path":"public","file":"site.zip","bytes":8340213,"human":"8.0 MB",
#    "extracted":true,"zip_kept":false}
FieldDefaultMeaning
file—The uploaded file (multipart/form-data), at most 256 MB
pathpublicDestination folder relative to the site root, e.g. public/app
extract1Extract a .zip automatically — send 0 to just store it
keep_zip0Keep the .zip after extracting (by default it is deleted, so the archive is not left published)

If the upload succeeds but extraction fails you get HTTP 207 with a warning — the file is already on the server, do not re-upload. An oversized file returns HTTP 413.

Example: create a site

curl -X POST -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"domain":"example.com","type":"php","db":"mariadb"}' \
  https://host-th-sv1.f4host.com/api/v1/sites

Databases & Cron db:* · cron:read

POST/sites/:domain/dbdb:writeCreate database {engine}
GET/sites/:domain/dbdb:readEvery database this site has (name/engine/host/port — no passwords)
POST/sites/:domain/db/importdb:importImport a dump (multipart) .sql / .sql.gz / .zip {file,database?,mode?}

Example: move a whole site — files and database

curl -X POST -H "Authorization: Bearer <token>" \
  -F "[email protected]" https://host-th-sv1.f4host.com/api/v1/sites/example.com/files
curl -X POST -H "Authorization: Bearer <token>" \
  -F "[email protected]" -F "mode=replace" \
  https://host-th-sv1.f4host.com/api/v1/sites/example.com/db/import
# → {"ok":true,"database":"example_com","engine":"mariadb","mode":"replace",
#    "tables":42,"snapshot":"/var/backups/hosting/.pre-import/example.com/..."}
FieldDefaultMeaning
file—The dump: .sql, .sql.gz, or a .zip holding exactly one .sql — at most 256 MB
databasethe site main databaseMust be a database this site owns; any other name is refused, not created
modemergemerge imports over what is there (tables absent from the dump survive) · replace empties the database first

The current contents are always dumped before an import and the path comes back as snapshot (the ten most recent are kept per site). If that snapshot cannot be taken, the import is refused.

GET/croncron:readList cron jobs

Members & SSO members:* · sso · provision

GET/membersmembers:readList members
GET/members/:idmembers:readOne member (plan, quota, owned domains, suspension state)
POST/membersmembers:writeCreate member (admin/reseller) {username,password,planId} — refused with 403 for a Private Hosting account
POST/members/:id/passwordmembers:passwordChange a customer's panel password {password}
POST/members/:id/planmembers:planMove to another plan {planId}
POST/members/:id/sshmembers:sshGrant/revoke a member's shell access {ssh:true|false} — administrator only; revoking removes their keys and cuts live sessions
POST/members/:id/suspendmembers:suspendSuspend the account — suspends every site the customer owns
POST/members/:id/unsuspendmembers:suspendUnsuspend the account
DELETE/members/:idmembers:deleteDelete account / cancel service — ?purge=1 also deletes all their sites
POST/ssossoThe “Log in to Panel” button — one-time login link, 120s TTL {user}
POST/provisionprovisionProvision in one call: member + site + optional WordPress + optional DNS zone + login link

Reseller members:* · plans:read

Every members endpoint above already works for a reseller, scoped to the customers they created (parent = that reseller). The three below exist to build a reseller portal.

GET/meany tokenWho you are, what is left of your allowance, and your customers in aggregate (any valid token)
GET/members/:id/usagemembers:readPer-customer usage: sites, disk and bandwidth against the plan limits
GET/plansplans:readThe plans you may sell — a reseller sees only what the administrator allowed, not the whole catalogue

A planId you are not allowed to sell returns 403 you may not assign that plan — on create, on plan change and on provision alike.

Example: sell a package and hand over a login link

curl -X POST -H "Authorization: Bearer <reseller-token>" \
  -H "Content-Type: application/json" \
  -d '{"username":"somchai","password":"a-long-password","domain":"somchai.com","planId":"pl_...","wordpress":true}' \
  https://host-th-sv1.f4host.com/api/v1/provision
# → {"ok":true,"memberCreated":true,"member":{...},"site":{...},
#    "wordpress":{...},"dns":null,"loginUrl":"https://.../sso/<once>","expires_in":120}

Example: a “Files” / “Database” button in your billing site

curl -X POST -H "Authorization: Bearer <token>" https://host-th-sv1.f4host.com/api/v1/sites/customer.com/filemanager
# → {"ok":true,"url":"https://.../sso/<once>","dest":"/site/customer.com/files?p=public"}
# open the returned url → the customer lands in the file manager, auto-logged-in, single-use

Example: auto-provision when an order is paid

curl -X POST -H "Authorization: Bearer <token>" -H "Content-Type: application/json" -d '{
  "username":"customer1","password":"S3cret-Passw0rd","planId":"pl_xxx",
  "domain":"customer-site.com","wordpress":{"title":"My shop","user":"admin","email":"[email protected]"},
  "dns":true }' https://host-th-sv1.f4host.com/api/v1/provision
# → {"ok":true,"member":{...},"site":{...},"wordpress":{"adminUrl":"..."},"loginUrl":"https://.../sso/<once>"}

Example: one customer lifecycle

// 1) provision
curl -X POST -H "Authorization: Bearer <token>" -H "Content-Type: application/json"   -d '{"username":"customer1","password":"S3cret-Passw0rd","planId":"pl_xxx"}' https://host-th-sv1.f4host.com/api/v1/members
// 2) log in button
curl -X POST ... -d '{"user":"customer1"}' https://host-th-sv1.f4host.com/api/v1/sso
// 3) reset password
curl -X POST ... -d '{"password":"N3w-Passw0rd"}' https://host-th-sv1.f4host.com/api/v1/members/<id>/password
// 4) unpaid → suspend
curl -X POST ... https://host-th-sv1.f4host.com/api/v1/members/<id>/suspend
// 5) cancel (delete everything)
curl -X DELETE ... "https://host-th-sv1.f4host.com/api/v1/members/<id>?purge=1"

Example: auto-login link (SSO)

curl -X POST -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" -d '{"user":"customer1"}' \
  https://host-th-sv1.f4host.com/api/v1/sso
# → {"ok":true,"url":"https://.../sso/<once>","expires_in":120}

Plans & Server plans:* · server:read

GET/plansplans:readList plans
POST/plansplans:writeCreate plan {name,maxSites,diskMB,bandwidthMB,maxAccounts,maxDatabases,maxSubdomains,maxAliases,maxFtpAccounts,cpuPercent,ramMB,phpWorkers,phpMemoryMB,maxExecSec,abuseGraceMin,abuseCooldownMin,whiteLabel}
POST/plans/:idplans:writeEdit a plan you own — send only the fields you want changed
DELETE/plans/:idplans:deleteDelete a plan you own — accounts on it are left with no plan rather than inheriting somebody else quotas

Plans have an owner: one a reseller creates belongs to that reseller, one an administrator creates is a system plan. A reseller sees and edits only their own, and maxAccounts is administrator-only — a reseller cannot mint another reseller.

Every quota is an integer and 0 means unlimited — except maxSites, where a negative value means unlimited.

FieldMeaning
maxSitesTotal sites on the account, subdomains included
diskMBTotal disk space (MB)
bandwidthMBMonthly traffic (MB)
maxAccountsSub-accounts a reseller may create
maxDatabasesDatabases across every site on the account
maxSubdomainsSubdomains — sites under a domain the account already owns
maxAliasesParked domains across all sites; the automatic www. is not counted
maxFtpAccountsFTP accounts across every site on the account
cpuPercentCPU per website as a percentage of ONE core (100 = a full core) · 0 = unlimited
ramMBRAM ceiling per website in MB · 0 = unlimited
phpWorkersConcurrent PHP requests per website (pm.max_children) · 0 = derived from ramMB
phpMemoryMBPHP memory_limit for a single request · 0 = derived automatically
maxExecSecPHP max_execution_time · 0 = PHP's own default
abuseGraceMinSuspend the site after this many consecutive minutes pinned at its ceiling · 0 = throttle only, never suspend
abuseCooldownMinAuto-restore this many minutes after suspension · 0 = an administrator has to lift it
whiteLabelLets the plan brand the panel as its own (settable by the server administrator only)
GET/server/statsserver:readServer stats
GET/pingany tokenTest token + who am I

F4 Panel · hosting control panel · developed by F4 Code