HF Files and HF Studio API
Script a hosting account's files with HF Files and its databases with HF Studio, using hff_ tokens from a normal sign-in.
HF Files (the File Manager) and HF Studio (the database browser) each have their own JSON API under /api/v1. Their
interfaces use it in the browser; your scripts can use it too, to upload a site, edit a file or run SQL as one hosting
account.
| App | Address |
|---|---|
| HF Files | https://files.yourdomain.com/api/v1/…, or https://panel.yourdomain.com:8443/files/api/v1/… |
| HF Studio | https://db.yourdomain.com/api/v1/…, or https://panel.yourdomain.com:8443/studio/api/v1/… |
GET /api/v1 on either app lists its addresses.
Note: these APIs work for one hosting account at a time and sign in as that account. To manage many accounts, use the token API with an
hf_token instead.hf_tokens do not work here, andhff_tokens do not work in the token API.
Signing in with an hff_ token
Scripts sign in with the account's username and password, adding "token": true. The answer holds a token that
starts with hff_. Send it as Authorization: Bearer hff_… on every later request. No cookie and no CSRF token are
needed.
F=https://files.yourdomain.com
curl -s $F/api/v1/login -H 'Content-Type: application/json' \
-d '{"username":"sarahbakes","password":"…","token":true}'
{
"ok": true,
"data": {
"csrf": "…",
"expires_in": 7200,
"token": "hff_…",
"user": {
"email": "sarah@example.com",
"staff": "",
"username": "sarahbakes"
}
}
}
With two-factor login switched on, the first answer is {"twofa": true, "csrf": "…", "token": "hff_…"}. Send the
code from the authenticator app (or a backup code) with that token within 5 minutes:
curl -s $F/api/v1/login/2fa -H "Authorization: Bearer hff_xxx" -H 'Content-Type: application/json' \
-d '{"code":"123456"}'
| Rule | What it means |
|---|---|
| Lifetime | a token lasts 2 hours without use, and 12 hours at most |
| Per app | an HF Files token works only in HF Files, an HF Studio token only in HF Studio |
| Restart | tokens live in memory; restarting the service signs every token out |
| Password change | changing the account's password ends its tokens |
| Suspension | a suspended or deleted account is signed out within a minute |
| Same sign-in rules as the panel | the same passwords, two-factor codes, lock-outs and activity log |
Only hosting customers sign in to HF Files and HF Studio. Sign out with POST /api/v1/logout.
Answers
Every answer is {"ok": true, "data": …} or {"ok": false, "error": "…"}.
| Status | Means |
|---|---|
| 200 | done |
| 400 | bad input |
| 401 | sign in again ("login": true) |
| 403 | not the account's file or database, or (browsers) a missing CSRF token |
| 404 | no such file, table or row |
| 409 | HF Studio: unlock the PostgreSQL database first ("unlock": true) |
| 413 | too big |
| 415 | HF Files: a change that is not JSON |
| 422 | refused; the message says why |
| 500, 502 | trouble on the server |
HF Files
path is always relative to the account's home folder, for example /web/example.com/public_html. name, from,
to and items are names inside path. Paths cannot leave the home folder, and /web, /logs, /tmp, /.ssh
and the home folder itself cannot be deleted or renamed.
| Method | Path | Body or query | Answer data |
|---|---|---|---|
| GET | /api/v1/files |
?path=/web |
path, parts, up, writable, items[] (name, type, dir, link, size, mtime, perms, writable, editable, image, zip) |
| GET | /api/v1/files/content |
?path=<file> |
path, dir, name, content, writable (text files up to 5 MB) |
| PUT | /api/v1/files/content |
{"path","content"} |
path, saved |
| GET | /api/v1/files/download |
?path=<file>[&inline=1] |
the file |
| GET | /api/v1/files/size |
?path=<file or folder> |
bytes |
| POST | /api/v1/files/upload |
?path=<folder>, multipart files[] (up to 1 GB per request) |
uploaded[], count; files of the same name are replaced |
| POST | /api/v1/files/folder |
{"path","name"} |
path |
| POST | /api/v1/files/file |
{"path","name"} |
path |
| POST | /api/v1/files/rename |
{"path","from","to"} |
path |
| POST | /api/v1/files/copy |
{"path","from","to"} |
path |
| POST | /api/v1/files/move |
{"path","items":[…],"dest"} |
moved, dest |
| POST | /api/v1/files/chmod |
{"path","name","mode":"644"} |
mode |
| POST | /api/v1/files/delete |
{"path","items":[…]} |
deleted |
| POST | /api/v1/files/zip |
{"path","items":[…],"name":"archive.zip"} |
name, path |
| POST | /api/v1/files/unzip |
{"path","name"} |
files |
| GET | /api/v1/shares |
[?path=<file or folder>] |
shares[], now |
| POST | /api/v1/shares |
{"path","hours","password","max_downloads"} |
the new share link |
| POST | /api/v1/shares/revoke |
{"id"} |
revoked |
For share links, hours is 1–720 (0 = never expires), an empty password means none, and max_downloads 0 means no
limit. Anyone can open a share link at https://files.yourdomain.com/s/<token> without signing in.
Example: upload and unpack a site
F=https://files.yourdomain.com
T=hff_xxx
P=/web/example.com/public_html
curl -s -H "Authorization: Bearer $T" -F 'files[]=@site.zip' "$F/api/v1/files/upload?path=$P"
curl -s -H "Authorization: Bearer $T" -H 'Content-Type: application/json' $F/api/v1/files/unzip \
-d "{\"path\":\"$P\",\"name\":\"site.zip\"}"
curl -s -H "Authorization: Bearer $T" "$F/api/v1/files?path=$P" | jq '.data.items[].name'
curl -s -H "Authorization: Bearer $T" -X POST $F/api/v1/logout
HF Studio
HF Studio opens only the account's own databases: MySQL/MariaDB (e=my), PostgreSQL (e=pg) and MongoDB (e=mg),
named <username>_…, and only kinds the account's plan includes. Any other database answers 403 "You can’t open
that database."
| Method | Path | Body or query | Answer data |
|---|---|---|---|
| GET | /api/v1/databases |
dbs[] (id, name, engine, size), pgReady, mgReady, limits, remote |
|
| GET | /api/v1/database |
?e=&db= |
engine, name, id, tables[], and error or password |
| GET | /api/v1/table/rows |
?e=&db=&t=[&p&n&s&d&q&fc&fo&fv] |
the table and one page of rows (browse) |
| GET | /api/v1/table/structure |
?e=&db=&t= |
the table with indexes, fks, create |
| POST | /api/v1/sql |
{"e","db","sql","limit"} |
results[], failed |
| POST | /api/v1/row |
{"e","db","t","key":{…}} |
cols, vals, long |
| POST | /api/v1/row/save, /row/add, /row/delete |
{"e","db","t","key":{…},"vals":{…}} |
{} |
| POST | /api/v1/unlock |
{"e":"pg","db","password"} |
{} |
| POST | /api/v1/import |
?e=&db=, multipart file (.sql or .sql.gz) |
file, done |
| GET | /api/v1/export |
?what=table|db&e=&db=[&t=]&fmt=sql|csv |
the download |
| GET | /api/v1/mongo/docs |
?db=&c=[&p&n&f&s&pr] |
the collection, docs[], fields, total, page, per |
| POST | /api/v1/mongo/doc, /doc/save, /doc/add, /doc/delete |
{"db","c","id","doc"} |
the document, or {"id"} |
| GET | /api/v1/mongo/indexes |
?db=&c= |
indexes[] |
| POST | /api/v1/mongo/query |
{"db","c","kind":"find|aggregate",…,"limit"} |
docs[], fields, more, error |
| POST | /api/v1/mongo/collection, /collection/drop |
{"db","name"} or {"db","c"} |
{"name"} or {} |
| POST | /api/v1/mongo/import |
?db=&c=, multipart file (.json, .jsonl, .csv, .gz) |
file, done |
| GET | /api/v1/mongo/export |
?db=&c=&fmt=json|jsonl|csv, or ?db=&what=db |
the download |
Limits: 30 seconds per statement, 1,000 rows per result, 200 statements per run, 1 MB of SQL text per run, and
imports up to the server's import limit (128 MB unless the administrator changed it). A failed SQL statement is not
an HTTP error: it is that result's error, with the line where the statement starts.
Example: run SQL
S=https://db.yourdomain.com
T=$(curl -s $S/api/v1/login -H 'Content-Type: application/json' \
-d '{"username":"sarahbakes","password":"…","token":true}' | jq -r .data.token)
curl -s -H "Authorization: Bearer $T" -H 'Content-Type: application/json' $S/api/v1/sql \
-d '{"e":"my","db":"sarahbakes_shop","sql":"SELECT id, name FROM products LIMIT 5;","limit":5}'
{
"ok": true,
"data": {
"results": [
{
"n": 1,
"line": 1,
"sql": "SELECT id, name FROM products LIMIT 5",
"ms": 2,
"cols": [
{"name": "id", "type": "INT", "bin": false},
{"name": "name", "type": "VARCHAR", "bin": false}
],
"rows": [
["1", "Sourdough loaf"],
["2", "Lemon drizzle"]
],
"more": false
}
],
"failed": false
}
}
Every change made through HF Files or HF Studio is written to the account's activity log.