PanelDOCSv1.0.0

Files and DB Studio API

Script a hosting account's files with Files and its databases with DB Studio, using hff_ tokens from a normal sign-in.

Files (the File Manager) and DB 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
Files https://fm.yourdomain.com/api/v1/…, or https://cp.yourdomain.com:8443/files/api/v1/…
DB Studio https://dbs.yourdomain.com/api/v1/…, or https://cp.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, and hff_ 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.

bash
F=https://fm.yourdomain.com
curl -s $F/api/v1/login -H 'Content-Type: application/json' \
     -d '{"username":"sarahbakes","password":"…","token":true}'
json
{
    "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:

bash
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 Files token works only in Files, an DB Studio token only in DB 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 Files and DB 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 DB Studio: unlock the PostgreSQL database first ("unlock": true)
413 too big
415 Files: a change that is not JSON
422 refused; the message says why
500, 502 trouble on the server

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; a file name with folders (site/css/main.css, as a dropped folder sends it) puts the file there and makes the missing folders
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"} (a copy in the same folder) or {"path","items":[…],"dest"} (into another folder, names kept) path, or copied, dest
POST /api/v1/files/move {"path","items":[…],"dest"} (a folder never into itself) 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://fm.yourdomain.com/s/<token> without signing in.

Example: upload and unpack a site

bash
F=https://fm.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

DB Studio

DB 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

bash
S=https://dbs.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}'
json
{
    "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 Files or DB Studio is written to the account's activity log.