Filesystem API
All filesystem endpoints require authentication.
Browse
Section titled “Browse”GET /api/fs/GET /api/fs/{path}Returns directory listing or file info depending on the path type.
Query parameters:
| Param | Type | Default | Description |
|---|---|---|---|
content | bool | false | Include text file content (up to 5 MB) |
sort | string | name | Sort by: name, size, modified, type |
order | string | asc | Sort order: asc, desc |
Directory response:
{ "path": "documents", "entries": [ { "name": "readme.md", "path": "documents/readme.md", "is_dir": false, "size": 1024, "modified": "2024-01-15T10:30:00Z", "mime": "text/markdown" } ], "total": 1, "truncated": false}File response:
{ "name": "readme.md", "path": "documents/readme.md", "is_dir": false, "size": 1024, "modified": "2024-01-15T10:30:00Z", "mime": "text/markdown", "content": "# Hello World\n...", "subtitles": []}For video files, the subtitles array lists detected subtitle files.
Save file
Section titled “Save file”PUT /api/fs/{path}Write content to a file. The write is atomic — content is written to a temp file and renamed.
Request: Raw bytes in the request body.
Response 200:
{ "message": "File saved", "path": "documents/readme.md" }Errors:
400— Path is a directory404— Parent directory doesn’t exist
Create directory
Section titled “Create directory”POST /api/fs/{path}Request body:
{ "type": "directory" }Response 201:
{ "message": "Directory created", "path": "documents/new-folder" }Delete
Section titled “Delete”DELETE /api/fs/{path}Deletes a file or directory. Directory deletion is recursive. The root path (/) is protected.
Response 200:
{ "message": "Deleted", "path": "documents/old-file.txt" }Rename / Move
Section titled “Rename / Move”PATCH /api/fs/{path}Request body:
{ "destination": "new/path/filename.txt", "overwrite": false}Response 200:
{ "message": "Renamed", "from": "old-name.txt", "to": "new-name.txt" }Errors:
409— Destination exists andoverwriteisfalse
Download
Section titled “Download”GET /api/fs/download/{path}Stream a file download. Supports HTTP range requests for partial content.
Query parameters:
| Param | Type | Default | Description |
|---|---|---|---|
inline | bool | false | Use Content-Disposition: inline instead of attachment |
Headers returned:
ETag— blake3 hash of file size + modification timeLast-Modified— file modification timestampCache-Control: private— auth-gated, not cached by CDNsAccept-Ranges: bytes— signals range request support
Range request example:
curl -H "Range: bytes=0-1023" \ -H "Authorization: Bearer TOKEN" \ http://localhost:8080/api/fs/download/video.mp4Returns 206 Partial Content with Content-Range header.
Search
Section titled “Search”GET /api/fs/searchFull-text filename search across the indexed filesystem. Powered by SQLite FTS5.
Query parameters:
| Param | Type | Default | Description |
|---|---|---|---|
q | string | (required) | Search term |
type | string | — | Filter by type: file, dir, image, video, audio, document |
min_size | integer | — | Minimum file size in bytes |
max_size | integer | — | Maximum file size in bytes |
after | string | — | Modified after (ISO 8601) |
before | string | — | Modified before (ISO 8601) |
path | string | — | Scope results to a subdirectory |
limit | integer | 50 | Results per page |
offset | integer | 0 | Pagination offset |
Response 200:
{ "results": [ { "name": "readme.md", "path": "documents/readme.md", "is_dir": false, "size": 1024, "modified": "2024-01-15T10:30:00Z", "mime_type": "text/markdown", "extension": "md" } ]}Example:
curl "http://localhost:8080/api/fs/search?q=config&type=file&min_size=100&limit=10" \ -H "Authorization: Bearer TOKEN"The search index is built on startup and kept in sync via filesystem watcher events with 500ms debounce.
Path safety
Section titled “Path safety”All paths are validated server-side:
- Path traversal (
../) is stripped - Only normal path components are allowed
- The resolved path must remain within the configured root directory
- Error messages use relative paths to avoid leaking server filesystem layout