API reference

HTTP protocol

theFinder works RPC-style over a single endpoint (for example /api/files). The UI speaks this protocol through TheFinderClient. If you are writing your own client or calling the API from another language, this document is all you need.

Request format#

RuleDetails
CommandChosen with the cmd parameter: GET /api/files?cmd=ls&id=... or { "cmd": "mkdir", ... } in a POST body.
MethodsGET, HEAD, POST. Anything else returns 405.
Read-only commandsinit, ls, tree, parents, info, size, search, file, thumb, download, get, trash, versions, version, stats. These can be called with GET and POST.
Mutating commandsEverything else works only with POST; calling them with GET returns BAD_REQUEST.
CSRF headerEvery POST request must carry an x-thefinder header (the value doesn't matter, the client sends 1). Otherwise 403 FORBIDDEN. Can be turned off with csrfProtection: false.
Bodyapplication/json, multipart/form-data or application/x-www-form-urlencoded. Query string parameters are read as well; if the same name is in the body, the body wins.
Lists (ids, vids)A JSON array, comma-separated text (ids=a,b) or a repeated query parameter (ids=a&ids=b). At most 10,000 items.
Booleanstrue, "true", "1" and 1 count as true.

Response format#

Commands that return JSON always use the same envelope:

{ "ok": true, "data": { ... } }
{ "ok": false, "error": { "code": "NOT_FOUND", "message": "File not found" } }

Responses are sent with Cache-Control: no-store. The file, thumb, download and version commands return the file itself instead of JSON (errors still use the JSON envelope).

Commands#

Types such as Entry and VolumeInfo in the tables are defined in core.md › Types.

Browsing#

CommandParametersdata
init—InitResult: { volumes, chunkSize, version, thumbnails, images }
lsid (folder){ cwd: Entry, entries: Entry[] }
treeid (folder){ entries: Entry[] }: subfolders only; each has hasDirs if the driver supports it
parentsid{ entries: Entry[] }: the chain from the volume root down to the item itself
infoids{ entries: Entry[] }
sizeids{ size, files, dirs }: folders are summed recursively
searchid (folder to search), q{ entries: Entry[], truncated: boolean }: searches subfolders too; case, accent and Turkish ı/İ insensitive. At most searchLimit results.

Creating and changing#

CommandParametersdata
mkdirid (parent folder), name{ entry }
mkfileid (parent folder), name, content?{ entry }
renameid, name{ entry, removed: [oldId] }: removed is empty if the name didn't change. Names that only change case (photo.JPG → photo.jpg) work on case-insensitive disks too.
duplicateids{ added: Entry[] }: copies are created in the same folder as "name (2)"
pasteids, dst (target folder), cut?, conflict?{ added: Entry[], removed: string[], skipped: string[] }

paste details:

Deleting and trash#

CommandParametersdata
rmids, permanent?{ removed: string[], trashed: Entry[] }: with the trash enabled items are moved to it; with permanent: true or trash: false they are deleted for good
trashvolume?{ entries: Entry[] }: the trash of every volume (or one), newest first. Each item has trash: { id, originalPath, deletedAt }; path is the original location.
restoreids (trash item ids){ restored: Entry[], removed: string[] }: goes back to its old place; the folder is recreated if missing, and the name becomes "name (2)" if taken
purgeids or all: true (+ volume?){ removed: string[], all: boolean }: deletes permanently

Trash item ids also use the <volume>_<base64url> format but point inside .tf-trash. They can only be used with restore and purge.

Uploading#

Files are sent in chunks (multipart/form-data). The chunk size is chunkSize from the init response.

FieldDescription
cmdupload
dstId of the target folder
nameFile name
sizeTotal size of the file (bytes)
index, totalChunk index (starting at 0) and total number of chunks
offsetByte offset of the chunk in the file
chunkThe chunk itself (a file field), at most chunkSize bytes
sessionEmpty on the first chunk; afterwards the session from the previous response
relativePath?For folder uploads, the file's relative folder ("photos/2024"); created under dst
conflict?"overwrite": overwrites a file with the same name, its old content goes into version history. If omitted, the new file becomes "name (2)".

Response: { done: false, session }, or on the last chunk { done: true, session, entry }.

Content#

CommandParametersResponse
fileid, download?, v?The file itself (inline or as an attachment). Range, ETag/304 and HEAD are supported. On S3 this may be a 302 redirect to a presigned URL. v is only a cache buster.
thumbid, size? (default 256), v?WebP thumbnail. The size is rounded to the nearest allowed value. If there is no thumbnail or it can't be generated, the original file is sent.
downloadidsA single file downloads directly. Several items or a folder download as a zip built on the fly (download.zip or <name>.zip).
getid{ content, bom, entry }: UTF-8 text. At most maxEditSize; UNSUPPORTED for non-text files.
putsee below{ entry, created }

put can be called in three ways:

FormFieldsResult
JSONid, contentOverwrites a text file
multipartid, file, reason?Overwrites with binary content (for example from the image editor)
multipartdst, name, fileSaves as a new file; if the name is taken a free one is chosen (created: true)

Before overwriting, the file's previous content is saved into version history. reason is a short word stored with the version (only a-z, at most 16 characters, default edit).

Archives#

CommandParametersdata
archiveids (must be in the same folder), name?{ entry }: the zip file; without a name, <name>.zip or Archive.zip
extractid (.zip){ entry, skipped }: extracts into a new folder named after the archive. Invalid entries and entries with disallowed extensions are skipped and counted.

Zip-slip protection is built in; if the total extracted size exceeds maxExtractSize the operation stops with TOO_LARGE.

Version history#

CommandParametersResponse
versionsid{ versions: FileVersion[], entry: Entry \| null }: newest first. If the file was deleted, entry is null but the history is still listed.
versionid, vid, download?The content of the version (a file response, cached for a year since it never changes)
revertid, vid{ entry, created }: replaces the file with that version; the current content becomes a version too. Recreates the file if it was deleted (created: true).
rmVersionsid, vids?{ removed, freed }: deletes the given versions, or the whole history without vids

Storage dashboard#

CommandParametersdata
statsvolumeStorageStats: scans the whole volume
cleanupvolume, target, …{ removed, freed }

cleanup options:

targetExtra parametersEffect
"cache"—Deletes the thumbnail cache (regenerated when needed)
"versions"mode: "all"All versions
"versions"mode: "orphaned"Versions whose file no longer exists
"versions"mode: "older", daysVersions older than days days
"versions"mode: "keep", keepKeeps only the last keep versions of each file

Bulk image processing#

transform only works when images is configured on the server; otherwise it returns UNSUPPORTED.

ParameterDescription
idsThe images
format?"keep" (default), "jpeg", "png", "webp", "avif", "gif"
width?, height?Fitted into this box; aspect ratio kept, never upscaled. At most 20,000.
quality?1–100, default 80
output?When the format stays the same: "overwrite" (default) or "copy" (name-<suffix>.ext)
skipLarger?Default true: the file is left alone if the result isn't smaller than the original
conflict?When the output name is taken: "rename" (default) or "overwrite"
suffix?Suffix for copies, default optimized
dryRun?true: encode only and report after, width, height (and skipped) without writing anything. Needs the same permissions as a real run.

Response: { results: TransformResult[] }. Each file reports its own result; an error in one file doesn't stop the others. When the format changes the original is kept and a new file is written next to it. Overwritten files have their old content saved into version history.

Error codes#

CodeHTTPWhen
BAD_REQUEST400Missing or invalid parameter, invalid id/path, mutating command over GET
UNAUTHORIZED401The authorize hook threw TheFinderError("UNAUTHORIZED")
UNKNOWN_COMMAND400Unknown cmd
NOT_FOUND404No such item, volume or trash item
EXISTS409An item with the same name exists
INVALID_NAME400Invalid name (\ / : * ? " < > \|, a trailing dot, names reserved by Windows, longer than 255 characters, reserved internal folder names)
NOT_A_DIRECTORY400A file where a folder was expected
NOT_A_FILE400A folder where a file was expected
FORBIDDEN403authorize returned false, permission denied it, CSRF header missing, no file system permission
READ_ONLY403The volume or the request is read-only
LOCKED403A volume root cannot be renamed, moved or deleted
EXTENSION_DENIED403The extension is not on the allow list or is on the deny list
TOO_LARGE413Upload, chunk, edit, image or archive limit exceeded
MOVE_INTO_ITSELF400Tried to move a folder into itself
UNSUPPORTED400A non-text file was requested with get, a non-.zip file was extracted, image processing is off on the server
INVALID_ARCHIVE400Corrupt zip
INVALID_IMAGE400The image could not be decoded
STORAGE502Storage error (S3 response, disk full)
INTERNAL500Unexpected error; details go to the server log with the [thefinder] prefix

File system errors are mapped to these codes as well: ENOENT → NOT_FOUND, EEXIST/ENOTEMPTY → EXISTS, EACCES/EPERM → FORBIDDEN, ENOSPC → STORAGE, ENAMETOOLONG → INVALID_NAME.

On top of these, the client (ApiError) can produce codes that don't come from the server: NETWORK (no connection or the response isn't JSON), ABORTED (upload cancelled), UPLOAD (upload didn't complete), UNKNOWN.

This page is generated from docs/api/protocol.md.