{"name":"Synology WebDAV Chunked Uploader API","version":"1.1.0","description":"Resilient chunked file uploader with direct WebDAV staging, 3x exponential backoff retry, and O(1) constant-memory stream merging.","baseUrl":"http://api.fileuploader.commandos.rozsnorbert.hu","authentication":{"type":"ApiKey","header":"x-api-key","description":"Pass the secret API key defined in .env in the \"x-api-key\" request header.","requiredEndpoints":["/upload/*","/download/*","/files/*","/health/key"],"publicEndpoints":["/","/docs","/health"],"queryParameterAuth":"GET /download* additionally accepts ?apiKey=<key> so plain browser links work (the key then appears in URLs/history: prefer the header).","keySources":["Static API_KEY from the server .env file","Keys created in the admin UI (/admin, requires ADMIN_PASSWORD): individually named, can expire and be disabled"]},"endpoints":[{"id":"health-check","method":"GET","path":"/health","summary":"Service Health Probe","description":"Returns the health status, uptime, and memory consumption of the Node.js API.","authRequired":false,"headers":{},"requestBody":null,"responses":{"200":{"description":"Service is operational and healthy","example":{"status":"healthy","timestamp":"2026-09-07T18:00:00.000Z","uptime":142.5,"memoryUsage":{"rss":65432100,"heapTotal":16023552,"heapUsed":12450800}}}},"curl":"curl -X GET \"http://api.fileuploader.commandos.rozsnorbert.hu/health\""},{"id":"upload-start","method":"POST","path":"/upload/start","summary":"Initialize Upload Session","description":"Creates a unique upload session on the server and prepares the remote WebDAV staging directory.","authRequired":true,"headers":{"Content-Type":"application/json","x-api-key":"your-api-key"},"requestBody":{"contentType":"application/json","fields":[{"name":"filename","type":"string","required":true,"description":"Original filename to upload (e.g., \"video.mp4\")"},{"name":"fileSize","type":"integer","required":false,"description":"Total file size in bytes. Recommended: enables the size-based chunk size recommendation (adaptive chunking)"},{"name":"totalChunks","type":"integer","required":false,"description":"Expected number of chunks (informational; with adaptive chunking the real count is only known at the end)"},{"name":"chunkSize","type":"integer","required":false,"description":"Fixed chunk size in bytes (max MAX_CHUNK_SIZE_MB). Omit it to use the recommended, adaptive size"},{"name":"targetPath","type":"string","required":false,"description":"WebDAV target directory. Must be inside WEBDAV_UPLOAD_DIR; relative paths are placed under it; \"..\" is rejected (400)"}],"example":{"filename":"dataset-sample.zip","fileSize":20971520,"targetPath":"/EpochMedia/videos"}},"responses":{"200":{"description":"Session initialized successfully","example":{"success":true,"uploadId":"a5496992-5356-4fa9-a383-0ef8b4c81d53","filename":"dataset-sample.zip","targetPath":"/EpochMedia","stagingDir":"/EpochMedia/.staging_a5496992-5356-4fa9-a383-0ef8b4c81d53","totalChunks":null,"chunkSize":null,"fileSize":20971520,"recommendedChunkSize":5242880,"minChunkSize":1048576,"maxChunkSize":52428800,"chunkTargetSeconds":8,"message":"Upload session initialized successfully."}},"400":{"description":"Missing filename, invalid numeric field, or targetPath outside the upload root / containing \"..\""},"401":{"description":"Unauthorized: Invalid or missing x-api-key header"}},"curl":"curl -X POST \"http://api.fileuploader.commandos.rozsnorbert.hu/upload/start\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"x-api-key: your-api-key\" \\\n  -d '{\"filename\": \"dataset-sample.zip\", \"totalChunks\": 4}'"},{"id":"upload-chunk","method":"POST","path":"/upload/chunk","summary":"Upload Chunk Part","description":"Uploads a single chunk slice directly to the WebDAV staging directory. Employs 3-attempt exponential backoff retry on network drops.","authRequired":true,"headers":{"Content-Type":"multipart/form-data","x-api-key":"your-api-key"},"requestBody":{"contentType":"multipart/form-data","fields":[{"name":"file","type":"binary/file","required":true,"description":"The binary chunk slice data"},{"name":"uploadId","type":"string (UUID)","required":true,"description":"The uploadId received from /upload/start"},{"name":"chunkIndex","type":"integer","required":true,"description":"0-indexed sequence number (0, 1, 2...)"},{"name":"totalChunks","type":"integer","required":false,"description":"Total chunk count (informational)"},{"name":"filename","type":"string","required":false,"description":"Original filename. REQUIRED when the session no longer exists (server restart), so the upload can resume"}],"example":"FormData { file: <Blob>, uploadId: \"a549...\", chunkIndex: \"0\", totalChunks: \"4\" }"},"responses":{"200":{"description":"Chunk uploaded successfully to WebDAV","example":{"success":true,"uploadId":"a5496992-5356-4fa9-a383-0ef8b4c81d53","chunkIndex":0,"size":5242880,"remotePath":"/EpochMedia/.staging_a549.../dataset-sample.zip.part0","speedBytesPerSec":3145728,"nextChunkSize":10485760,"message":"Chunk 0 uploaded successfully to WebDAV."}},"400":{"description":"Missing file, invalid uploadId (must be a UUID) or invalid chunkIndex"},"401":{"description":"Unauthorized"}},"curl":"curl -X POST \"http://api.fileuploader.commandos.rozsnorbert.hu/upload/chunk\" \\\n  -H \"x-api-key: your-api-key\" \\\n  -F \"uploadId=a5496992-5356-4fa9-a383-0ef8b4c81d53\" \\\n  -F \"chunkIndex=0\" \\\n  -F \"file=@part0.bin\""},{"id":"upload-finish","method":"POST","path":"/upload/finish","summary":"Finalize & Stream-Merge File","description":"Downloads all .part chunks from WebDAV, sequentially merges them via local stream pipeline (O(1) RAM usage), streams the final assembled file to WebDAV destination, and deletes staging parts. Before merging it verifies on WebDAV that exactly the parts 0..totalChunks-1 exist.","authRequired":true,"headers":{"Content-Type":"application/json","x-api-key":"your-api-key"},"requestBody":{"contentType":"application/json","fields":[{"name":"uploadId","type":"string (UUID)","required":true,"description":"Active upload session identifier"},{"name":"totalChunks","type":"integer","required":true,"description":"Total number of chunk parts to merge"},{"name":"filename","type":"string","required":false,"description":"Filename fallback if session was restarted"},{"name":"targetPath","type":"string","required":false,"description":"Destination directory on WebDAV"}],"example":{"uploadId":"a5496992-5356-4fa9-a383-0ef8b4c81d53","totalChunks":4,"filename":"dataset-sample.zip"}},"responses":{"200":{"description":"File assembled and uploaded to WebDAV successfully","example":{"success":true,"uploadId":"a5496992-5356-4fa9-a383-0ef8b4c81d53","filename":"dataset-sample.zip","destination":"/EpochMedia/dataset-sample.zip","fileSize":20971520,"totalChunks":4,"message":"File successfully assembled and uploaded to WebDAV."}},"400":{"description":"Missing/invalid uploadId or totalChunks"},"409":{"description":"Upload incomplete or totalChunks does not match the uploaded parts. Nothing is merged or deleted; the response details list missing/unexpected chunk indexes"}},"curl":"curl -X POST \"http://api.fileuploader.commandos.rozsnorbert.hu/upload/finish\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"x-api-key: your-api-key\" \\\n  -d '{\"uploadId\": \"a5496992-5356-4fa9-a383-0ef8b4c81d53\", \"totalChunks\": 4}'"},{"id":"upload-failed","method":"POST","path":"/upload/failed","summary":"Abort Upload & Cleanup","description":"Triggered if chunk upload fails after retries. Immediately deletes all uploaded .part files and staging directories from WebDAV.","authRequired":true,"headers":{"Content-Type":"application/json","x-api-key":"your-api-key"},"requestBody":{"contentType":"application/json","fields":[{"name":"uploadId","type":"string (UUID)","required":true,"description":"Upload session identifier to abort"},{"name":"filename","type":"string","required":false,"description":"Filename fallback"}],"example":{"uploadId":"a5496992-5356-4fa9-a383-0ef8b4c81d53"}},"responses":{"200":{"description":"Staging resources cleaned and session aborted","example":{"success":true,"uploadId":"a5496992-5356-4fa9-a383-0ef8b4c81d53","status":"aborted","cleaned":true,"message":"Upload aborted and all temporary staging chunks deleted from WebDAV."}}},"curl":"curl -X POST \"http://api.fileuploader.commandos.rozsnorbert.hu/upload/failed\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"x-api-key: your-api-key\" \\\n  -d '{\"uploadId\": \"a5496992-5356-4fa9-a383-0ef8b4c81d53\"}'"},{"id":"upload-status","method":"GET","path":"/upload/status/:uploadId","summary":"Query Upload Status","description":"Returns the current progress, received chunk indexes, and timestamps of an active upload session.","authRequired":true,"headers":{"x-api-key":"your-api-key"},"requestBody":null,"responses":{"200":{"description":"Session status record","example":{"uploadId":"a5496992-5356-4fa9-a383-0ef8b4c81d53","filename":"dataset-sample.zip","status":"uploading","uploadedChunksCount":3,"uploadedChunkIndexes":[0,1,2],"totalChunks":4,"createdAt":"2026-09-07T18:00:00.000Z","lastActivityAt":"2026-09-07T18:00:15.000Z"}}},"curl":"curl -X GET \"http://api.fileuploader.commandos.rozsnorbert.hu/upload/status/a5496992-5356-4fa9-a383-0ef8b4c81d53\" \\\n  -H \"x-api-key: your-api-key\""},{"id":"download-file","method":"GET","path":"/download","summary":"Stream File Download with Range Support","description":"Directly streams a file from WebDAV storage to client with O(1) memory usage. Supports HTTP Range (206 Partial Content) for resumable downloads / video seeking, and ?inline=true for in-browser media display. Supports authentication via x-api-key header or ?apiKey= query parameter.","authRequired":true,"headers":{"x-api-key":"your-api-key (or ?apiKey=your-api-key)","Range":"bytes=0-1048575 (Optional for partial/resumable chunks)"},"queryParams":[{"name":"path","type":"string","required":true,"description":"WebDAV remote path or relative filename to download (e.g., \"/EpochMedia/dataset.zip\" or \"dataset.zip\"). Must be inside WEBDAV_UPLOAD_DIR, otherwise 403"},{"name":"inline","type":"boolean","required":false,"description":"Set to \"true\" for inline browser rendering (e.g. video playback, images) instead of attachment download"},{"name":"apiKey","type":"string","required":false,"description":"Alternative to x-api-key header for direct browser links or <a> tags"}],"responses":{"200":{"description":"Full binary stream returned with Content-Disposition and Content-Type headers"},"206":{"description":"Partial binary chunk returned with Content-Range header for resumable downloads"},"401":{"description":"Unauthorized: Invalid or missing API key"},"404":{"description":"File not found on WebDAV"},"416":{"description":"Requested range not satisfiable"}},"curl":"curl -X GET \"http://api.fileuploader.commandos.rozsnorbert.hu/download?path=/EpochMedia/dataset-sample.zip\" \\\n  -H \"x-api-key: your-api-key\" \\\n  -o \"dataset-sample.zip\""},{"id":"download-info","method":"GET","path":"/download/info","summary":"Query File Metadata","description":"Retrieves file existence, size, MIME type, ETag, and last modified timestamp from WebDAV storage prior to downloading.","authRequired":true,"headers":{"x-api-key":"your-api-key"},"queryParams":[{"name":"path","type":"string","required":true,"description":"WebDAV file path to inspect (e.g., \"/EpochMedia/dataset-sample.zip\")"}],"responses":{"200":{"description":"File metadata record","example":{"success":true,"file":{"path":"/EpochMedia/dataset-sample.zip","filename":"dataset-sample.zip","size":20971520,"mimeType":"application/zip","lastModified":"2026-09-07T18:05:00.000Z","etag":"\"abc123\"","type":"file"}}},"401":{"description":"Unauthorized"},"404":{"description":"File not found on WebDAV"}},"curl":"curl -X GET \"http://api.fileuploader.commandos.rozsnorbert.hu/download/info?path=/EpochMedia/dataset-sample.zip\" \\\n  -H \"x-api-key: your-api-key\""},{"id":"download-list","method":"GET","path":"/download/list","summary":"Browse WebDAV Directory","description":"Lists all files and subdirectories located inside a specified WebDAV folder.","authRequired":true,"headers":{"x-api-key":"your-api-key"},"queryParams":[{"name":"path","type":"string","required":false,"description":"Directory path to browse (defaults to configured WEBDAV_UPLOAD_DIR)"}],"responses":{"200":{"description":"Array of directory items","example":{"success":true,"directory":"/EpochMedia","count":2,"items":[{"name":"dataset-sample.zip","path":"/EpochMedia/dataset-sample.zip","type":"file","size":20971520,"mimeType":"application/zip","lastModified":"2026-09-07T18:05:00.000Z"},{"name":"subfolder","path":"/EpochMedia/subfolder","type":"directory","size":null,"mimeType":"inode/directory","lastModified":"2026-09-07T18:00:00.000Z"}]}},"401":{"description":"Unauthorized"},"404":{"description":"Directory not found"}},"curl":"curl -X GET \"http://api.fileuploader.commandos.rozsnorbert.hu/download/list?path=/EpochMedia\" \\\n  -H \"x-api-key: your-api-key\""},{"id":"delete-file","method":"DELETE","path":"/files","summary":"Delete File or Folder","description":"Deletes a file (or, with recursive=true, a folder with all its content) inside the folder of the API key. A key bound to a folder can only delete inside that folder; keys without a folder act on the whole WEBDAV_UPLOAD_DIR. The key folder itself and internal .staging_* folders cannot be deleted.","authRequired":true,"headers":{"x-api-key":"your-api-key"},"requestBody":{"contentType":"query parameters","fields":[{"name":"path","type":"string","required":true,"description":"File or folder path, relative to the key folder (or absolute inside it). Also accepted as DELETE /files/<path>"},{"name":"recursive","type":"boolean","required":false,"description":"Must be true to delete a folder"}],"example":"DELETE /files?path=videos/dataset.mp4"},"responses":{"200":{"description":"Deleted","example":{"success":true,"deleted":{"path":"/EpochMedia/ugyfel1/videos/dataset.mp4","type":"file"},"message":"file deleted."}},"400":{"description":"Missing path"},"401":{"description":"Unauthorized (the x-api-key header is required; query-string keys are not accepted)"},"403":{"description":"Path outside the key folder, contains \"..\", targets the key folder itself or a .staging_* folder"},"404":{"description":"File or folder not found"},"409":{"description":"Target is a folder and recursive=true was not given"}},"curl":"curl -X DELETE \"http://api.fileuploader.commandos.rozsnorbert.hu/files?path=videos/dataset.mp4\" -H \"x-api-key: your-api-key\""},{"id":"key-health","method":"GET","path":"/health/key","summary":"API Key Health","description":"Status report limited to the calling API key: the key itself (name, folder, expiry, last use; never the secret), the state and size of its WebDAV folder and its own active uploads. No server-wide data (uptime, memory, other keys or folders). Add deep=true for a recursive (bounded) size scan of the folder.","authRequired":true,"headers":{"x-api-key":"your-api-key"},"requestBody":{"contentType":"query parameters","fields":[{"name":"deep","type":"boolean","required":false,"description":"true = count files recursively (at most 200 folder listings); default: top level only"}],"example":"GET /health/key"},"responses":{"200":{"description":"Key status. \"status\" is \"degraded\" if the WebDAV folder is not reachable or the key is disabled/expired","example":{"status":"healthy","timestamp":"2026-11-03T10:00:00.000Z","key":{"type":"managed","id":"b1c2...","name":"Customer 1","folder":"ugyfel1","enabled":true,"expiresAt":null,"lastUsedAt":"2026-11-03T09:59:00.000Z"},"storage":{"rootDir":"/EpochMedia/ugyfel1","reachable":true,"exists":true,"scope":"top-level","complete":true,"fileCount":12,"folderCount":0,"totalBytes":52428800},"uploads":{"active":0,"sessions":[]}}},"401":{"description":"Unauthorized (x-api-key header required)"}},"curl":"curl \"http://api.fileuploader.commandos.rozsnorbert.hu/health/key\" -H \"x-api-key: your-api-key\""},{"id":"api-docs","method":"GET","path":"/docs","summary":"API Specification & Interactive Explorer","description":"Returns this interactive API documentation page (HTML) or complete JSON schema (Accept: application/json).","authRequired":false,"headers":{},"requestBody":null,"responses":{"200":{"description":"HTML Documentation or JSON schema"}},"curl":"curl -X GET \"http://api.fileuploader.commandos.rozsnorbert.hu/docs\" -H \"Accept: application/json\""}]}