docs(openapi): add file storage API specifications

Add 13 endpoints for chunked upload, file management, and folder CRUD with 6 new schemas.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
dailz
2026-04-15 09:30:18 +08:00
parent c0176d7764
commit 20576bc325

View File

@@ -23,6 +23,18 @@
{
"name": "Applications",
"description": "Application definition CRUD and job submission"
},
{
"name": "File Uploads",
"description": "Chunked file upload with SHA256 dedup, breakpoint resume, and instant upload"
},
{
"name": "Files",
"description": "File listing, metadata, download (with Range support), and deletion"
},
{
"name": "Folders",
"description": "Folder CRUD with materialized path hierarchy"
}
],
"paths": {
@@ -1103,6 +1115,649 @@
}
}
}
},
"/files/uploads": {
"post": {
"tags": ["File Uploads"],
"operationId": "initUpload",
"summary": "Initialize a chunked upload session",
"description": "Creates an upload session for chunked file upload. If a file with the same SHA256 already exists, returns the existing file immediately (instant upload / dedup).",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/InitUploadRequest"
}
}
}
},
"responses": {
"201": {
"description": "Upload session created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UploadSessionResponse"
}
}
}
},
"200": {
"description": "Dedup hit - file already exists, returns existing file",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FileResponse"
}
}
}
},
"400": {
"description": "Invalid request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResponseError"
}
}
}
},
"500": {
"description": "Internal server error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResponseError"
}
}
}
}
}
}
},
"/files/uploads/{sessionId}/chunks/{chunkIndex}": {
"put": {
"tags": ["File Uploads"],
"operationId": "uploadChunk",
"summary": "Upload a single chunk",
"description": "Uploads a file chunk for the specified session. Chunks can be uploaded in any order and are idempotent.",
"parameters": [
{
"$ref": "#/components/parameters/SessionId"
},
{
"$ref": "#/components/parameters/ChunkIndex"
}
],
"requestBody": {
"required": true,
"content": {
"multipart/form-data": {
"schema": {
"type": "object",
"required": ["chunk"],
"properties": {
"chunk": {
"type": "string",
"format": "binary",
"description": "File chunk data"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Chunk uploaded successfully"
},
"400": {
"description": "Invalid request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResponseError"
}
}
}
},
"404": {
"description": "Upload session not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResponseError"
}
}
}
},
"500": {
"description": "Internal server error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResponseError"
}
}
}
}
}
}
},
"/files/uploads/{sessionId}/complete": {
"post": {
"tags": ["File Uploads"],
"operationId": "completeUpload",
"summary": "Complete an upload session",
"description": "Finalizes the upload by merging all chunks via ComposeObject. Supports retry if previously failed.",
"parameters": [
{
"$ref": "#/components/parameters/SessionId"
}
],
"responses": {
"201": {
"description": "Upload completed, returns file",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FileResponse"
}
}
}
},
"400": {
"description": "Invalid request or missing chunks",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResponseError"
}
}
}
},
"404": {
"description": "Upload session not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResponseError"
}
}
}
},
"500": {
"description": "Internal server error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResponseError"
}
}
}
}
}
}
},
"/files/uploads/{sessionId}": {
"get": {
"tags": ["File Uploads"],
"operationId": "getUploadStatus",
"summary": "Get upload session status",
"description": "Returns the current status of an upload session including list of uploaded chunk indices.",
"parameters": [
{
"$ref": "#/components/parameters/SessionId"
}
],
"responses": {
"200": {
"description": "Upload session status",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UploadSessionResponse"
}
}
}
},
"404": {
"description": "Upload session not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResponseError"
}
}
}
},
"500": {
"description": "Internal server error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResponseError"
}
}
}
}
}
},
"delete": {
"tags": ["File Uploads"],
"operationId": "cancelUpload",
"summary": "Cancel an upload session",
"description": "Cancels an in-progress upload, deletes uploaded chunks from MinIO, and removes the session.",
"parameters": [
{
"$ref": "#/components/parameters/SessionId"
}
],
"responses": {
"200": {
"description": "Upload cancelled successfully"
},
"404": {
"description": "Upload session not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResponseError"
}
}
}
},
"500": {
"description": "Internal server error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResponseError"
}
}
}
}
}
}
},
"/files": {
"get": {
"tags": ["Files"],
"operationId": "listFiles",
"summary": "List files with pagination",
"parameters": [
{
"name": "page",
"in": "query",
"schema": { "type": "integer", "default": 1 }
},
{
"name": "page_size",
"in": "query",
"schema": { "type": "integer", "default": 20 }
},
{
"name": "folder_id",
"in": "query",
"required": false,
"description": "Filter by folder ID",
"schema": { "type": "integer", "format": "int64" }
},
{
"name": "search",
"in": "query",
"required": false,
"description": "Search files by name",
"schema": { "type": "string" }
}
],
"responses": {
"200": {
"description": "List of files",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FileListResponse"
}
}
}
},
"500": {
"description": "Internal server error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResponseError"
}
}
}
}
}
}
},
"/files/{id}": {
"get": {
"tags": ["Files"],
"operationId": "getFile",
"summary": "Get file metadata",
"parameters": [
{
"$ref": "#/components/parameters/FileId"
}
],
"responses": {
"200": {
"description": "File metadata",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FileResponse"
}
}
}
},
"404": {
"description": "File not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResponseError"
}
}
}
},
"500": {
"description": "Internal server error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResponseError"
}
}
}
}
}
},
"delete": {
"tags": ["Files"],
"operationId": "deleteFile",
"summary": "Delete a file",
"description": "Soft-deletes a file. If no other files reference the same blob, the blob is removed from MinIO.",
"parameters": [
{
"$ref": "#/components/parameters/FileId"
}
],
"responses": {
"200": {
"description": "File deleted successfully"
},
"404": {
"description": "File not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResponseError"
}
}
}
},
"500": {
"description": "Internal server error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResponseError"
}
}
}
}
}
}
},
"/files/{id}/download": {
"get": {
"tags": ["Files"],
"operationId": "downloadFile",
"summary": "Download a file",
"description": "Downloads the file content. Supports HTTP Range header for partial content delivery.",
"parameters": [
{
"$ref": "#/components/parameters/FileId"
},
{
"name": "Range",
"in": "header",
"required": false,
"description": "Byte range for partial content",
"example": "bytes=0-1023",
"schema": { "type": "string" }
}
],
"responses": {
"200": {
"description": "Full file content",
"content": {
"application/octet-stream": {
"schema": {
"type": "string",
"format": "binary"
}
}
}
},
"206": {
"description": "Partial content",
"content": {
"application/octet-stream": {
"schema": {
"type": "string",
"format": "binary"
}
}
}
},
"404": {
"description": "File not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResponseError"
}
}
}
},
"500": {
"description": "Internal server error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResponseError"
}
}
}
}
}
}
},
"/files/folders": {
"post": {
"tags": ["Folders"],
"operationId": "createFolder",
"summary": "Create a folder",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateFolderRequest"
}
}
}
},
"responses": {
"201": {
"description": "Folder created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FolderResponse"
}
}
}
},
"400": {
"description": "Invalid request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResponseError"
}
}
}
},
"500": {
"description": "Internal server error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResponseError"
}
}
}
}
}
},
"get": {
"tags": ["Folders"],
"operationId": "listFolders",
"summary": "List folders",
"parameters": [
{
"name": "parent_id",
"in": "query",
"required": false,
"description": "Parent folder ID (null for root)",
"schema": { "type": "integer", "format": "int64" }
}
],
"responses": {
"200": {
"description": "List of folders",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/FolderResponse"
}
}
}
}
},
"500": {
"description": "Internal server error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResponseError"
}
}
}
}
}
}
},
"/files/folders/{id}": {
"get": {
"tags": ["Folders"],
"operationId": "getFolder",
"summary": "Get folder details",
"parameters": [
{
"$ref": "#/components/parameters/FolderId"
}
],
"responses": {
"200": {
"description": "Folder details",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FolderResponse"
}
}
}
},
"404": {
"description": "Folder not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResponseError"
}
}
}
},
"500": {
"description": "Internal server error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResponseError"
}
}
}
}
}
},
"delete": {
"tags": ["Folders"],
"operationId": "deleteFolder",
"summary": "Delete a folder",
"description": "Deletes an empty folder. Cannot delete folders that contain files or sub-folders.",
"parameters": [
{
"$ref": "#/components/parameters/FolderId"
}
],
"responses": {
"200": {
"description": "Folder deleted successfully"
},
"400": {
"description": "Folder is not empty",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResponseError"
}
}
}
},
"404": {
"description": "Folder not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResponseError"
}
}
}
},
"500": {
"description": "Internal server error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResponseError"
}
}
}
}
}
}
}
},
"components": {
@@ -1143,6 +1798,46 @@
"type": "integer",
"format": "int64"
}
},
"SessionId": {
"name": "sessionId",
"in": "path",
"required": true,
"description": "Upload session ID",
"schema": {
"type": "integer",
"format": "int64"
}
},
"ChunkIndex": {
"name": "chunkIndex",
"in": "path",
"required": true,
"description": "Chunk index (0-based)",
"schema": {
"type": "integer",
"minimum": 0
}
},
"FileId": {
"name": "id",
"in": "path",
"required": true,
"description": "File ID",
"schema": {
"type": "integer",
"format": "int64"
}
},
"FolderId": {
"name": "id",
"in": "path",
"required": true,
"description": "Folder ID",
"schema": {
"type": "integer",
"format": "int64"
}
}
},
"schemas": {
@@ -1789,6 +2484,198 @@
"description": "Items per page"
}
}
},
"InitUploadRequest": {
"type": "object",
"required": ["file_name", "file_size", "sha256", "chunk_size"],
"properties": {
"file_name": {
"type": "string",
"description": "File name"
},
"file_size": {
"type": "integer",
"format": "int64",
"description": "Total file size in bytes"
},
"sha256": {
"type": "string",
"description": "SHA256 hash of the entire file (hex encoded)"
},
"chunk_size": {
"type": "integer",
"format": "int64",
"description": "Chunk size in bytes (must be >= min_chunk_size config)"
},
"folder_id": {
"type": "integer",
"format": "int64",
"description": "Target folder ID (null for root)",
"nullable": true
}
}
},
"UploadSessionResponse": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int64",
"description": "Session ID"
},
"file_name": {
"type": "string",
"description": "File name"
},
"file_size": {
"type": "integer",
"format": "int64",
"description": "Total file size in bytes"
},
"chunk_size": {
"type": "integer",
"format": "int64",
"description": "Chunk size in bytes"
},
"total_chunks": {
"type": "integer",
"description": "Total number of chunks"
},
"sha256": {
"type": "string",
"description": "SHA256 hash of the entire file"
},
"status": {
"type": "string",
"enum": ["pending", "uploading", "merging", "completed", "failed", "cancelled", "expired"],
"description": "Session status"
},
"uploaded_chunks": {
"type": "array",
"items": {
"type": "integer"
},
"description": "List of uploaded chunk indices"
},
"expires_at": {
"type": "string",
"format": "date-time",
"description": "Session expiration time"
}
}
},
"FileResponse": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int64",
"description": "File ID"
},
"name": {
"type": "string",
"description": "File name"
},
"folder_id": {
"type": "integer",
"format": "int64",
"description": "Parent folder ID",
"nullable": true
},
"size": {
"type": "integer",
"format": "int64",
"description": "File size in bytes"
},
"mime_type": {
"type": "string",
"description": "MIME type"
},
"sha256": {
"type": "string",
"description": "SHA256 hash"
},
"created_at": {
"type": "string",
"format": "date-time"
},
"updated_at": {
"type": "string",
"format": "date-time"
}
}
},
"FileListResponse": {
"type": "object",
"properties": {
"files": {
"type": "array",
"items": {
"$ref": "#/components/schemas/FileResponse"
},
"description": "List of files"
},
"total": {
"type": "integer",
"description": "Total number of matching files"
},
"page": {
"type": "integer",
"description": "Current page number"
},
"page_size": {
"type": "integer",
"description": "Items per page"
}
}
},
"FolderResponse": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int64",
"description": "Folder ID"
},
"name": {
"type": "string",
"description": "Folder name"
},
"parent_id": {
"type": "integer",
"format": "int64",
"description": "Parent folder ID (null for root)",
"nullable": true
},
"path": {
"type": "string",
"description": "Materialized path (e.g. /data/reports/)"
},
"created_at": {
"type": "string",
"format": "date-time"
},
"updated_at": {
"type": "string",
"format": "date-time"
}
}
},
"CreateFolderRequest": {
"type": "object",
"required": ["name"],
"properties": {
"name": {
"type": "string",
"description": "Folder name (no path traversal characters)"
},
"parent_id": {
"type": "integer",
"format": "int64",
"description": "Parent folder ID (null for root)",
"nullable": true
}
}
}
}
}