Files
Browse, read, upload, rename and delete the files of a site.
List a folder
GET/api/v1/sites/{site_id}/filesScope files:read
Folders first, then files; up to 5,000 entries. Symbolic links are listed (type "link") but never followed. Available under the same rule as the File Manager: the site is installed, the plan is active and the site is not suspended.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
site_id | path | integer | yes | The site id (see GET /sites). |
path | query | string | no | Folder path relative to the site folder (default: the site folder itself). |
Example
curl -s "https://app.pbn.ltd/api/v1/sites/123/files?path=wp-content" \
-H "Authorization: Bearer $PBN_API_KEY"
import os
import requests
headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.get("https://app.pbn.ltd/api/v1/sites/123/files?path=wp-content", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/files?path=wp-content", {
method: "GET",
headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
Response
200
{
"data": {
"path": "wp-content",
"items": [
{
"name": "themes",
"type": "folder",
"size": null,
"modified": "2026-09-01T10:00:00Z",
"mode": "644",
"path": "wp-content/themes"
}
],
"truncated": false
}
}
Errors: conflict, forbidden, not_found, rate_limited, scope_missing, unauthorized
File or folder details
GET/api/v1/sites/{site_id}/files/statScope files:read
Type, size, modification time and mode of one path.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
site_id | path | integer | yes | The site id (see GET /sites). |
path | query | string | yes | Path relative to the site folder, e.g. "wp-content/uploads" ("" or "/" = the site folder). |
Example
curl -s "https://app.pbn.ltd/api/v1/sites/123/files/stat?path=index.php" \
-H "Authorization: Bearer $PBN_API_KEY"
import os
import requests
headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.get("https://app.pbn.ltd/api/v1/sites/123/files/stat?path=index.php", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/files/stat?path=index.php", {
method: "GET",
headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
Response
200
{
"data": {
"name": "index.php",
"type": "file",
"size": 405,
"modified": "2026-09-01T10:00:00Z",
"mode": "644",
"path": "index.php"
}
}
Errors: not_found, rate_limited, scope_missing, unauthorized
Read (download) a file
GET/api/v1/sites/{site_id}/files/contentScope files:read
Returns a file of up to 5 MB (the limit is in GET /limits). Use encoding=raw to stream the bytes directly.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
site_id | path | integer | yes | The site id (see GET /sites). |
path | query | string | yes | Path relative to the site folder, e.g. "wp-content/uploads" ("" or "/" = the site folder). |
encoding | query | string (one of: base64, text, raw) | no | base64 (JSON, any file), text (JSON, UTF-8 files) or raw (the bytes themselves, with a Content-Type). Default: base64. |
Example
curl -s "https://app.pbn.ltd/api/v1/sites/123/files/content?path=robots.txt&encoding=text" \
-H "Authorization: Bearer $PBN_API_KEY"
import os
import requests
headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.get("https://app.pbn.ltd/api/v1/sites/123/files/content?path=robots.txt&encoding=text", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/files/content?path=robots.txt&encoding=text", {
method: "GET",
headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
Response
200
{
"data": {
"path": "robots.txt",
"size": 67,
"modified": "2026-09-01T10:00:00Z",
"content_type": "text/plain",
"content": "User-agent: *\nDisallow:\n",
"encoding": "text"
}
}
Errors: not_found, rate_limited, scope_missing, too_large, unauthorized
Upload (write) a file
PUT/api/v1/sites/{site_id}/files/contentScope files:write
Writes a file of up to 25 MB atomically (a temporary file renamed into place), owned by the site's web server user. Refuses to write through symbolic links and into folders the platform manages.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
site_id | path | integer | yes | The site id (see GET /sites). |
path | query | string | yes | Where to write it, relative to the site folder. |
content | body | string | no | Text content (UTF-8). Or use content_base64. |
content_base64 | body | string | no | The file, base64-encoded. Or send the raw bytes as the request body (Content-Type application/octet-stream) or a multipart form with a "file" field. |
overwrite | query | boolean | no | Replace an existing file. Default: False. |
mkdirs | query | boolean | no | Create missing folders. Default: True. |
Example
curl -s -X PUT "https://app.pbn.ltd/api/v1/sites/123/files/content?path=hello.txt&overwrite=true" \
-H "Authorization: Bearer $PBN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"content": "Hello world\n"}'
import os
import requests
headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.put("https://app.pbn.ltd/api/v1/sites/123/files/content?path=hello.txt&overwrite=true", headers=headers, json={"content": "Hello world\n"}, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/files/content?path=hello.txt&overwrite=true", {
method: "PUT",
headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`, "Content-Type": "application/json"},
body: JSON.stringify({"content": "Hello world\n"})
});
console.log(res.status, await res.json());
Response
201
{
"data": {
"name": "hello.txt",
"type": "file",
"size": 12,
"modified": "2026-09-01T10:00:00Z",
"mode": "644",
"path": "hello.txt",
"written": 12
}
}
Errors: conflict, forbidden, not_found, rate_limited, scope_missing, too_large, unauthorized
Create a folder
POST/api/v1/sites/{site_id}/files/foldersScope files:write
Creates a folder (and any missing parent folders).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
site_id | path | integer | yes | The site id (see GET /sites). |
path | body | string | yes | The new folder path. |
Example
curl -s -X POST "https://app.pbn.ltd/api/v1/sites/123/files/folders" \
-H "Authorization: Bearer $PBN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"path": "wp-content/uploads/reports"}'
import os
import requests
headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.post("https://app.pbn.ltd/api/v1/sites/123/files/folders", headers=headers, json={"path": "wp-content/uploads/reports"}, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/files/folders", {
method: "POST",
headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`, "Content-Type": "application/json"},
body: JSON.stringify({"path": "wp-content/uploads/reports"})
});
console.log(res.status, await res.json());
Response
201
{
"data": {
"name": "new",
"type": "folder",
"size": null,
"modified": "2026-09-01T10:00:00Z",
"mode": "755",
"path": "wp-content/new"
}
}
Errors: conflict, not_found, rate_limited, scope_missing, unauthorized
Rename or move
POST/api/v1/sites/{site_id}/files/moveScope files:write
Renames or moves a file or folder inside the site folder.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
site_id | path | integer | yes | The site id (see GET /sites). |
path | body | string | yes | What to move. |
to | body | string | yes | The new path. |
overwrite | body | boolean | no | Replace an existing file at the destination. Default: False. |
Example
curl -s -X POST "https://app.pbn.ltd/api/v1/sites/123/files/move" \
-H "Authorization: Bearer $PBN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"path": "hello.txt", "to": "old/hello.txt"}'
import os
import requests
headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.post("https://app.pbn.ltd/api/v1/sites/123/files/move", headers=headers, json={"path": "hello.txt", "to": "old/hello.txt"}, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/files/move", {
method: "POST",
headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`, "Content-Type": "application/json"},
body: JSON.stringify({"path": "hello.txt", "to": "old/hello.txt"})
});
console.log(res.status, await res.json());
Response
200
{
"data": {
"name": "index.php",
"type": "file",
"size": 405,
"modified": "2026-09-01T10:00:00Z",
"mode": "644",
"path": "index.php",
"moved_from": "old.php"
}
}
Errors: conflict, not_found, rate_limited, scope_missing, unauthorized
Delete a file or folder
DELETE/api/v1/sites/{site_id}/filesScope files:write · destructive
Deletes a file, or a folder (with recursive=true when it is not empty). There is no undo - take a backup first for anything important.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
site_id | path | integer | yes | The site id (see GET /sites). |
path | query | string | yes | Path relative to the site folder, e.g. "wp-content/uploads" ("" or "/" = the site folder). |
recursive | query | boolean | no | Required to delete a folder that is not empty. Default: False. |
Example
curl -s -X DELETE "https://app.pbn.ltd/api/v1/sites/123/files?path=hello.txt" \
-H "Authorization: Bearer $PBN_API_KEY"
import os
import requests
headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.delete("https://app.pbn.ltd/api/v1/sites/123/files?path=hello.txt", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/files?path=hello.txt", {
method: "DELETE",
headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
Response
200
{
"data": {
"path": "old.zip",
"deleted": true,
"entries_removed": 1
}
}
Errors: conflict, not_found, rate_limited, scope_missing, unauthorized