A tiny, unauthenticated file-drop service for a trusted LAN. Everything is
plain HTTP against a single server (app.py). There are no tokens, cookies, or
sessions — anyone who can reach the host can use every endpoint.
- Base URL:
http://HOST:PORT(default port8000) - Paths in requests are always relative to the share root (
SHARE_DIR), use/separators, and never start with/...and absolute paths are rejected (safe_join). The root itself is the empty string"". - JSON in, JSON out for mutating calls; raw bytes for upload/download/zip.
- Errors return a non-2xx status with body
{"error": "<message>"}.
| Method | Path | Purpose | Body |
|---|---|---|---|
| GET | /api/list |
list a folder | — |
| GET | /api/download |
download one file | — |
| GET | /api/view |
display a txt/html file inline | — |
| PUT | /api/upload |
upload one file (raw stream) | raw file bytes |
| POST | /api/mkdir |
create a folder (incl. parents) | JSON |
| POST | /api/delete |
delete a file or folder (recursive) | JSON |
| POST | /api/rename |
rename in place | JSON |
| POST | /api/move |
move into another folder | JSON |
| POST | /api/zip |
stream a ZIP of files/folders | JSON or form |
List the contents of a folder. path= (empty) lists the root.
curl -fsS "http://HOST:8000/api/list?path=docs"Response:
{
"path": "docs",
"items": [
{"name": "2026", "path": "docs/2026", "size": 60,
"modified": 1782782062, "is_dir": true},
{"name": "report.pdf", "path": "docs/report.pdf", "size": 20480,
"modified": 1782782062, "is_dir": false}
],
"disk": {"total": 8020152320, "free": 7158226944, "used": 861925376}
}Folders sort first, then files; both alphabetical (case-insensitive).
modified is a Unix timestamp (seconds). 404 if the folder doesn't exist.
Download a single file. Streams raw bytes with a Content-Disposition
attachment header. 404 if the path is not a file.
curl -fSL -o report.pdf "http://HOST:8000/api/download?path=docs/report.pdf"Serve a file inline so the browser renders it instead of downloading.
Only .txt (as text/plain) and .html/.htm (as text/html) are served
inline; any other extension falls back to the /api/download behaviour.
404 if the path is not a file.
Unless the server runs with HTML_SANDBOX=0, HTML responses carry a
Content-Security-Policy: sandbox header: the page renders, but scripts and
forms are blocked. The installer asks which mode you want.
curl -fsS "http://HOST:8000/api/view?path=docs/notes.txt"Upload one file. The request body is the raw file content (not multipart).
path= is the full destination including the filename. Missing parent
folders are created automatically. An existing file at that path is overwritten.
curl -fSL -T report.pdf "http://HOST:8000/api/upload?path=docs/2026/report.pdf"- URL-encode
pathif it contains spaces/special characters (%20, etc.). - The upload streams to a
<name>.parttemp file, then atomically renames on completion; a dropped connection leaves no partial file. - Honors
MAX_UPLOAD(bytes) if the server set it →413when exceeded. - Response:
{"ok": true, "path": "docs/2026/report.pdf"}.
Create a folder. Nested paths are created in one call (like mkdir -p).
Already-existing folders are a no-op (success).
curl -fsS -X POST "http://HOST:8000/api/mkdir" \
-H "Content-Type: application/json" \
-d '{"path":"docs/2026/invoices"}'Response: {"ok": true, "path": "docs/2026/invoices"}.
Delete a file, or a folder and all its contents (recursive). Cannot delete the root. 404 if the path doesn't exist.
curl -fsS -X POST "http://HOST:8000/api/delete" \
-H "Content-Type: application/json" \
-d '{"path":"docs/old"}'Response: {"ok": true}.
Rename an item in place (same parent folder). name is the new basename —
it may not contain /, ., or ... Fails if the target name already exists.
curl -fsS -X POST "http://HOST:8000/api/rename" \
-H "Content-Type: application/json" \
-d '{"path":"docs/report.pdf","name":"report-final.pdf"}'Response: {"ok": true, "path": "docs/report-final.pdf"}.
Move an item into a different existing folder (dest must be a folder; use
"" for the root). Keeps the item's basename. Refuses to move a folder into
itself/a descendant, or onto an existing name in the destination.
curl -fsS -X POST "http://HOST:8000/api/move" \
-H "Content-Type: application/json" \
-d '{"path":"docs/report.pdf","dest":"archive"}'Response: {"ok": true, "path": "archive/report.pdf"}.
Stream a ZIP archive of one or more files/folders (folders are included
recursively). Accepts either JSON {"paths": [...]} or a form POST with
repeated path= fields. A single selected folder names the archive after it;
otherwise it's mysharedbucket.zip.
curl -fSL -X POST "http://HOST:8000/api/zip" \
-H "Content-Type: application/json" \
-d '{"paths":["docs","photos/cat.jpg"]}' \
-o bundle.zip400 if the selection resolves to no files.
HOST=myserver.local:8000
# upload
curl -fSL -T "$f" "http://$HOST/api/upload?path=$(python3 -c 'import urllib.parse,sys;print(urllib.parse.quote(sys.argv[1],safe="/"))' "$dest")"
# make folder
curl -fsS -X POST "http://$HOST/api/mkdir" -H 'Content-Type: application/json' -d "{\"path\":\"$dir\"}"
# list (pipe through jq for readability)
curl -fsS "http://$HOST/api/list?path=$dir" | jq .There is a convenience wrapper upload.sh (host normalization + URL-encoding)
in the repo, but it is gitignored as a local helper — re-create it from the
/api/upload section above if it's missing.
Security: no auth by design. Run only on a trusted LAN; never expose to the internet.