Files
The following paths are relative to /api/v1 and require authentication.
Endpoints
Section titled “Endpoints”| Method | Path | Description |
|---|---|---|
PUT | /files/upload/{upload_id}/body | Single-request streaming body upload after init |
POST | /files/new | Create an empty file |
POST | /files/upload/init | Negotiate upload mode |
GET | /files/upload/sessions | List recoverable upload sessions |
PUT | /files/upload/{upload_id}/{chunk_number} | Upload one chunk |
POST | /files/upload/{upload_id}/presign-parts | Request object-storage / remote multipart part URLs |
POST | /files/upload/{upload_id}/complete | Assemble chunks or confirm presigned upload |
GET | /files/upload/{upload_id} | Read upload progress |
DELETE | /files/upload/{upload_id} | Cancel upload |
GET | /files/{id} | Read file metadata |
GET | /files/{id}/archive-preview | Read read-only archive preview manifest |
GET | /files/{id}/direct-link | Create direct-download token |
POST | /files/{id}/preview-link | Create short-lived preview link |
POST | /files/{id}/resource-handle | Resolve a frontend-consumable file resource contract |
POST | /files/{id}/wopi/open | Create WOPI launch session |
GET | /files/{id}/download | Download file content |
GET | /files/{id}/thumbnail | Get thumbnail |
GET | /files/{id}/image-preview | Get WebP image preview |
GET | /files/{id}/media-metadata | Get image / audio / video metadata |
PUT | /files/{id}/content | Overwrite content and write version history |
POST | /files/{id}/extract | Create archive extraction task |
PATCH | /files/{id} | Rename or move file |
DELETE | /files/{id} | Soft-delete to trash |
POST | /files/{id}/lock | Lock / unlock file |
POST | /files/{id}/copy | Copy file |
GET | /files/{id}/versions | List versions |
POST | /files/{id}/versions/{version_id}/restore | Restore a version |
DELETE | /files/{id}/versions/{version_id} | Delete a version |
Uploads
Section titled “Uploads”Primary upload entries:
POST /files/new: create a product-level empty filePOST /files/upload/init: negotiate mode firstPUT /files/upload/{upload_id}/body: streams a rawapplication/octet-streambody to the target fixed by init without a full-file server staging copyGET /files/upload/sessions: recover unfinished sessions after refresh
Creation and upload parameters include:
POST /files/new:folder_idandrelative_pathrequest body fieldsPOST /files/newand its team counterpart accept an optionalIdempotency-Keyheader. Keys are limited to 255 bytes and cannot contain ASCII whitespace.- The legacy
POST /files/uploadmultipart endpoint is removed; nonempty files must initialize a session first. POST /files/upload/init:relative_pathandfrontend_client_id
folder_id = null means root. Missing directories in relative_path are created automatically. Empty path segments such as docs//bad.txt are rejected.
GET /files/upload/sessions is a separate recoverable-session listing endpoint. Its optional frontend_client_id query parameter filters sessions created by the same frontend instance; it does not accept folder_id or relative_path and does not perform directory uploads.
Negotiation returns one of five transports:
stream: single-request upload through the initialized sessionchunked: resumable chunked uploadpresigned: single object-storage or remote presignedPUTpresigned_multipart: object-storage or remote multipart direct upload; the client must request part URLs separatelyprovider_resumable: provider-native resumable upload session
The frontend never sees an additional relay_stream mode. Actual transfer strategy is decided by storage connectors and policy options:
options.object_storage_upload_strategy: transfer strategy for S3-compatible, Azure Blob, and Tencent COS object-storage connectorsoptions.remote_upload_strategy- OneDrive uses Microsoft Graph native upload capabilities and follows the upload workflow exposed by the connector
relay_stream:initreturnsstream/chunked; the server relays bytes straight to object storage or a followerpresigned:initreturnspresigned/presigned_multipart
Before creating a session, provider upload, or directory side effect, init performs target-capacity admission. Local, OneDrive, and Remote targets that advertise capacity observation use their current available bytes: an exact fit is accepted, while a conclusively insufficient target is excluded and placement tries the next candidate. Exhausting conclusively insufficient candidates returns upload.target_capacity_insufficient (HTTP 507). An unavailable observation with no remaining target returns retryable upload.capacity_unavailable (HTTP 503). S3-compatible, OSS, COS, Qiniu, Huawei OBS, Azure Blob, and SFTP have no portable capacity API; unsupported is a normal capability result and init proceeds while relying on the actual data-plane outcome.
Target-capacity admission is a fast-fail snapshot, not a cross-request space reservation. The final workspace quota remains protected by the completion transaction’s SQL CAS. Capacity observation is demand-driven rather than periodically scanned: concurrent probes for one policy are coalesced, total probe concurrency is bounded, and each driver owns its freshness and timeout policy. Local filesystem observations are fresh for two seconds and may serve sufficient results for up to 30 seconds; OneDrive and Remote observations are fresh for 30 seconds and may serve sufficient results for up to five minutes with stale-while-revalidate. Stale insufficient or unavailable results are refreshed before rejection. Local probes use a fixed two-second timeout; OneDrive and Remote default to ten seconds and expose a two-to-30-second connector setting. Temporary failures use short driver-owned negative caching, and a shared probe survives cancellation of its initiating request.
Sessions requiring OffsetStaging / StreamStaging serialize temporary-disk admission against upload_temp_dir before Init returns and physically preallocate the complete file. Allocation must leave server.upload_temp_min_free_bytes free (256 MiB by default); insufficient space returns upload.staging_capacity_insufficient (HTTP 507) without leaving a session or temporary directory. After restart, the first staged Init, Chunk PUT, or Complete restores missing physical allocation for active sessions. Completion, cancellation, and expiry cleanup release reservations by deleting their temporary directories.
Object-storage and remote uploads fall back to relay_stream by default. Legacy {"presigned_upload":true} and {"s3_upload_strategy":"presigned"} are accepted as compatibility inputs for object-storage presigned upload; new clients should send {"object_storage_upload_strategy":"presigned"}.
Presigned browser uploads require usable CORS on the object storage or follower internal storage endpoint. Azure Blob presigned upload uses SAS URLs and requires x-ms-blob-type: BlockBlob; S3-compatible, Tencent COS, and Remote multipart parts usually require returned ETags. Remote presigned upload only works for directly reachable remote nodes; reverse-tunnel remote nodes reject remote_upload_strategy = "presigned".
Stream, chunked, and completion stages
Section titled “Stream, chunked, and completion stages”POST /files/new: the canonical empty-file API. The file picker and folder-upload queue call it directly for 0-byteFilevalues without initializing an upload session. Its body acceptsfolder_idandrelative_path.PUT /files/upload/{upload_id}/body: accepts exactly one raw body for astreamsession. Filename, MIME, declared size, placement, and transport come only from init; duplicate bodies are rejected.- A successful stream body atomically creates file/blob metadata, updates quota, and marks the session
completed(file_id), then returns201 FileInfo; stream does not call/complete. GET /files/upload/sessions: lists unexpired, recoverable sessions inuploading/assembling/presignedstatus;frontend_client_idcan filter sessions created by the same frontend instancePUT /files/upload/{upload_id}/{chunk_number}: uploads one chunk, withchunk_numberstarting at0POST /files/upload/{upload_id}/presign-parts: used only forpresigned_multipartGET /files/upload/{upload_id}: returns upload progress used by resumable uploadPOST /files/upload/{upload_id}/complete: completeschunked,presigned,presigned_multipart, orprovider_resumable
Recoverable session fields include:
upload_idmodestatusfilenametotal_sizechunk_sizetotal_chunksreceived_countfolder_idchunks_on_diskcompleted_partsexpires_atupdated_at
Completion behavior:
- local path: validates size and quota; if local
content_dedupis enabled, computes SHA-256 and deduplicates blobs - object-storage / OneDrive / Remote paths: validate size and quota but do not deduplicate; each upload creates an independent blob using an upload-session-derived opaque hash and
files/{upload_id}-style object path
POST /files/new is metadata-only empty-file creation. files.blob_id remains non-null and points to the current storage policy’s canonical virtual_empty blob. That blob has size = 0, the standard empty-content SHA-256, and storage_path = NULL; creation does not call connector exists, put, get, or delete. Empty files in one policy share the blob. Downloads and ranges return deterministic empty content from memory, and purging the last reference removes only blob metadata. The first nonempty overwrite switches the file to an ordinary stored blob through the existing upload-finalization transaction.
An Idempotency-Key is isolated by authenticated actor, workspace kind/id, and operation, and only its SHA-256 is persisted. Replaying the same normalized request within 24 hours returns the original file/blob IDs without republishing the file-created event or audit. A different filename, base folder, normalized relative path, or target-name mode returns 409. Missing-parent creation, the canonical virtual blob, file metadata, and the idempotency result commit in one writer transaction. A replay whose retained result file was already purged also returns stable 409. Requests without a key preserve automatic rename behavior.
The browser persists one key per zero-byte upload task for 23 hours. Retries, token refresh, request timeout, and page restore reuse it; success, cancellation, and explicit terminal-task cleanup remove the record.
WebDAV PUT/LOCK staging, storage migration, internal-storage ingress, remote-follower writes, and prepared-blob restore/copy paths continue using zero-length object primitives when their protocols explicitly require a real object.
presigned_multipart completion must include object-storage returned parts; other modes may omit the body.
File operations
Section titled “File operations”GET /files/{id}: read metadata; trashed files behave as not foundGET /files/{id}/archive-preview: read archive manifest; returns202and queuesarchive_preview_generateif not readyGET /files/{id}/direct-link: returns a short token; real download is/d/{token}/{filename}POST /files/{id}/preview-link: returns a short preview link; real content is/pv/{token}/{filename}POST /files/{id}/resource-handle: resolves the resource URL, credentials, cache identity, and redirect policy for the requested purpose and representationPOST /files/{id}/wopi/open: creates a WOPI launch session for a configured WOPI previewerGET /files/{id}/download: streams file content or redirects to a presigned GET URL when policy says so; supportsIf-None-MatchGET /files/{id}/thumbnail: returns thumbnail, or202withRetry-Afterwhile generatingGET /files/{id}/image-preview: returns raw WebP withETag, or202withRetry-Afterwhile generatingGET /files/{id}/media-metadata: returns blob-cached metadata, or202while queuedPUT /files/{id}/content: overwrite existing content, check locks, create version history, and return a newETagPOST /files/{id}/extract: creates an archive extraction taskPATCH /files/{id}: rename or moveDELETE /files/{id}: soft-delete to trash
File info and list items include persisted classification fields:
extension: lowercase final extension without dotcompound_extension: lowercase compound extension such astar.gzfile_category:image,video,audio,document,spreadsheet,presentation,archive,code, orother
These fields are recalculated on create, upload, overwrite, and rename.
Detail responses from GET /files/{id} and GET /teams/{team_id}/files/{id} also include storage_used. This is the quota-accounting size for the file detail view: current size plus all historical version sizes. Directory list items omit this field.
Revision history
Section titled “Revision history”Every file has a canonical revision from creation. Successful overwrites through REST, uploads, WebDAV, or WOPI append immutable revisions; handlers do not maintain separate history tables.
GET /files/{id}/versionslists current and historical revisions in descending sequence order. Uselimit(default 100, maximum 1000) andafter_sequencefor stable keyset pagination.POST /files/{id}/versions/{version_id}/restoreappends a new current head whose content and user dead-property snapshot come from the target. Existing revisions remain intact.DELETE /files/{id}/versions/{version_id}applies only to a historical revision. It purges the content reference while preserving the public identity and sequence as a tombstone, and repairs the successor’s predecessor link.
Revision sequences increase monotonically and are never reused, so gaps are expected. COPY creates an independent history; MOVE/rename, trash, and trash restore retain the existing history. files is the current materialized projection and is counted once for logical quota and blob references.
POST /files/{id}/resource-handle
Section titled “POST /files/{id}/resource-handle”File details answer what a file is. A resource handle answers how the current client should consume it. Request example:
{ "purpose": "preview", "delivery_mode": "blob_url", "representation": "auto"}Allowed values:
purpose:preview,download,external_viewerdelivery_mode:blob_url,text,direct_url,media_stream,iframe_session,manifestrepresentation:auto,original,image_preview,thumbnail; omitted values default toauto
Core response contract:
{ "code": "success", "msg": "", "data": { "identity": { "cache_key": "/api/v1/files/42/download", "etag": "\"blob-hash\"", "scope": "personal" }, "request": { "url": "/api/v1/files/42/download?disposition=inline", "credentials": "include", "conditional_headers": "allowed", "redirect_policy": "same_origin_only" }, "delivery": { "mode": "blob_url", "mime_type": "image/jpeg" } }}identity.cache_key is the stable resource identity, while request.url is the concrete URL for this request. etag, conditional_headers, and credentials tell the frontend whether conditional requests and login credentials are appropriate. purpose = "download" resolves attachment disposition; preview and external-viewer purposes resolve inline disposition. redirect_policy = "may_cross_origin" means the URL can be a short-lived OneDrive, object-storage, or connector-defined delivery URL. Short-lived results also include optional lifecycle.expires_at and must not be used as stable cache identities.
For auto + preview + blob_url, browser-hostile image formats such as HEIC, RAW, and TIFF may resolve to a derived WebP image-preview path. Sandbox-sensitive MIME types such as HTML remain on the same-origin path rather than bypassing isolation through a provider direct URL.
PATCH /files/{id}
Section titled “PATCH /files/{id}”Request:
{ "name": "renamed.pdf", "folder_id": 5}Supports rename, move, and folder_id = null to move to root. Name conflicts at the destination are rejected, and locked files cannot be modified.
Thumbnails
Section titled “Thumbnails”Thumbnail support comes from the media processing registry and is exposed anonymously through /public/thumbnail-support. The built-in images processor covers common image formats. The built-in lofty processor can expose audio suffixes for embedded cover thumbnails. Optional vips_cli / ffmpeg_cli processors contribute additional extensions only when enabled and available.
Storage policies can also contribute storage-native thumbnail and image-preview support through the core policy behavior fields storage_native_thumbnail_enabled = true and storage_native_thumbnail_extensions. A connector descriptor only advertises whether this behavior is supported; it does not carry a second enablement switch. Setting storage_native_thumbnail_enabled = false disables only the provider-native candidate: files on the policy still continue through the global media-processing registry. The extension list remains stored as dormant configuration, so re-enabling restores the saved matching rules. Built-in tencent_cos policies can expose this through COS CI; built-in Local, S3-compatible, Azure Blob, OneDrive, and Remote policies do not expose native thumbnail or image-preview capabilities.
Thumbnails return WebP and reuse cache by blob, processor, processor version, and effective max dimension. The max source byte limit is controlled by thumbnail_max_source_bytes; the rendered longest edge is controlled by runtime config thumbnail_max_dimension.
The default thumbnail dimension keeps the legacy cache version namespace. Non-default dimensions add a -d{dimension} suffix to the derivative version, so changing the configured size does not overwrite or accidentally reuse a different-size cache entry.
Image previews
Section titled “Image previews”Image preview endpoints return larger WebP images for preview panels and are separate from thumbnails:
- thumbnails are list/card-oriented and may return
202 - image previews are previewer-oriented; cache hits return raw WebP, while cache misses enqueue
image_preview_generateand return202withRetry-After - image previews use runtime config
image_preview_max_dimensionfor the rendered longest edge - unsupported types return file/thumbnail-domain errors instead of falling back to original bytes
- the frontend can choose its default strategy from
/public/frontend-configfieldmedia.image_preview_preference
The supported image-preview extensions are the same public capability union advertised by /public/thumbnail-support under image thumbnail/preview support. They can come from backend media processors or a storage-native provider such as Tencent COS when the policy opts in. Image-preview caches follow the same dimension-aware derivative-version rule as thumbnails.
Media metadata
Section titled “Media metadata”Media metadata is cached by blob. Image metadata is read by the built-in images processor, audio by lofty, and video by ffprobe_cli. media_metadata_enabled is the master switch, while per-kind settings live in media_processing_registry_json.
Storage-native media metadata can be enabled per policy with the core policy behavior fields storage_native_media_metadata_enabled = true and storage_native_media_metadata_extensions. A connector descriptor only advertises support. Built-in tencent_cos policies can expose native audio/video metadata through COS CI; built-in Local, S3-compatible, Azure Blob, OneDrive, and Remote policies do not expose native media metadata. Setting the flag to false disables only provider-native extraction; the ordinary global metadata processors remain available. Extension lists are retained while a behavior is disabled, so temporarily disabling provider-native processing does not discard its matching configuration. An enabled behavior with an empty extension list is valid but matches no files.
Audio embedded cover art is exposed through the existing thumbnail path when the lofty processor has thumbnail:audio.
Archive preview
Section titled “Archive preview”GET /files/{id}/archive-preview returns a read-only manifest for supported archive files without extracting them into the workspace.
Optional filename_encoding controls ZIP entry-name decoding:
autoutf8gb18030cp437cp850shift_jisbig5euc_krwindows_1252
Explicit values override auto detection.
Response shape:
{ "code": "success", "msg": "", "data": { "schema_version": 2, "format": "zip", "source_blob_id": 42, "source_hash": "abc...", "generated_at": "2026-05-18T12:00:00Z", "entry_count": 2, "file_count": 1, "directory_count": 1, "total_uncompressed_size": 128, "truncated": false, "entries": [ { "path": "docs/readme.txt", "name": "readme.txt", "parent": "docs", "kind": "file", "size": 128, "compressed_size": 64, "modified_at": "2026-05-18T12:00:00Z" } ] }}Current implementation:
- supports
.zipand corresponding MIME types - disabled by default; requires both
archive_preview_enabledandarchive_preview_user_enabled - first uncached request queues or reuses
archive_preview_generateand returns202 - raw manifest is cached under
entity_propertiesassystem.archive_preview / zip_raw_manifest.v2 - success responses include
ETagand supportIf-None-Match - limits are controlled by archive-preview and archive-extraction runtime settings
- range-capable storage drivers are used for metadata scanning when possible
Direct and preview links
Section titled “Direct and preview links”GET /files/{id}/direct-link returns only a token. The actual URL is:
/d/{token}/{filename}POST /files/{id}/preview-link returns PreviewLinkInfo; actual content is served from:
/pv/{token}/{filename}These root-level endpoints return raw file data or redirects instead of wrapped JSON.