Skip to content

Filesystem API

All filesystem endpoints require authentication.

GET /api/fs/
GET /api/fs/{path}

Returns directory listing or file info depending on the path type.

Query parameters:

ParamTypeDefaultDescription
contentboolfalseInclude text file content (up to 5 MB)
sortstringnameSort by: name, size, modified, type
orderstringascSort 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.


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 directory
  • 404 — Parent directory doesn’t exist

POST /api/fs/{path}

Request body:

{ "type": "directory" }

Response 201:

{ "message": "Directory created", "path": "documents/new-folder" }

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" }

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 and overwrite is false

GET /api/fs/download/{path}

Stream a file download. Supports HTTP range requests for partial content.

Query parameters:

ParamTypeDefaultDescription
inlineboolfalseUse Content-Disposition: inline instead of attachment

Headers returned:

  • ETag — blake3 hash of file size + modification time
  • Last-Modified — file modification timestamp
  • Cache-Control: private — auth-gated, not cached by CDNs
  • Accept-Ranges: bytes — signals range request support

Range request example:

Terminal window
curl -H "Range: bytes=0-1023" \
-H "Authorization: Bearer TOKEN" \
http://localhost:8080/api/fs/download/video.mp4

Returns 206 Partial Content with Content-Range header.

GET /api/fs/search

Full-text filename search across the indexed filesystem. Powered by SQLite FTS5.

Query parameters:

ParamTypeDefaultDescription
qstring(required)Search term
typestringFilter by type: file, dir, image, video, audio, document
min_sizeintegerMinimum file size in bytes
max_sizeintegerMaximum file size in bytes
afterstringModified after (ISO 8601)
beforestringModified before (ISO 8601)
pathstringScope results to a subdirectory
limitinteger50Results per page
offsetinteger0Pagination 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:

Terminal window
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.


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