{
  "openapi": "3.0.3",
  "info": {
    "title": "MiOffice API",
    "description": "AI workspace API for PDF, image, video, and AI processing. Files processed server-side with async queue support. All processing is privacy-first.",
    "version": "1.0.0",
    "contact": {
      "name": "MiOffice",
      "url": "https://mioffice.ai"
    }
  },
  "servers": [
    {
      "url": "https://mioffice.ai",
      "description": "Production"
    }
  ],
  "paths": {
    "/api/pdf-process": {
      "post": {
        "operationId": "processPdf",
        "summary": "Process PDF files",
        "description": "Merge, split, compress PDFs or convert DOCX/XLSX to PDF. Returns processed file or queues job for async processing.",
        "tags": ["PDF"],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": ["action"],
                "properties": {
                  "action": {
                    "type": "string",
                    "enum": ["merge", "split", "compress", "docToPdf", "xlsxToPdf"],
                    "description": "PDF operation to perform"
                  },
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "Single file for split/compress/convert operations"
                  },
                  "file_0": {
                    "type": "string",
                    "format": "binary",
                    "description": "First file for merge (supports file_0 through file_99)"
                  },
                  "file_1": {
                    "type": "string",
                    "format": "binary",
                    "description": "Second file for merge"
                  },
                  "options": {
                    "type": "string",
                    "description": "JSON string with action-specific options. Split: {\"pages\": \"1,2,5-10\"}"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Processed PDF file",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            },
            "headers": {
              "X-Processing": {
                "schema": { "type": "string", "example": "server" }
              }
            }
          },
          "202": {
            "description": "Job queued for async processing",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/QueuedJob" }
              }
            }
          },
          "400": { "description": "Invalid action, missing files, or validation error" },
          "413": { "description": "Total file size exceeds 100MB limit" },
          "429": { "description": "Rate limit exceeded or queue full" }
        }
      }
    },
    "/api/image-process": {
      "post": {
        "operationId": "processImage",
        "summary": "Process images",
        "description": "Compress, resize, rotate, or convert images between formats (HEIC, PNG, JPG, WebP, AVIF).",
        "tags": ["Image"],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": ["file", "action"],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "Image file to process"
                  },
                  "action": {
                    "type": "string",
                    "enum": ["heicToJpg", "heicToPng", "compress", "resize", "rotate", "convert", "webpToPng", "webpToJpg", "pngToWebp", "jpgToWebp", "compressWebp", "avifToJpg"],
                    "description": "Image operation to perform"
                  },
                  "options": {
                    "type": "string",
                    "description": "JSON string. Resize: {\"width\":800,\"height\":600,\"fit\":\"contain\",\"quality\":0.9}. Rotate: {\"angle\":90}. Compress: {\"quality\":0.85,\"format\":\"jpeg\"}"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Processed image file",
            "content": {
              "image/*": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "202": {
            "description": "Job queued for async processing",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/QueuedJob" }
              }
            }
          },
          "400": { "description": "Invalid action, missing file, or non-image file" },
          "413": { "description": "File exceeds 25MB limit" },
          "429": { "description": "Rate limit exceeded or queue full" }
        }
      }
    },
    "/api/ai-process": {
      "post": {
        "operationId": "processAi",
        "summary": "AI-powered image processing",
        "description": "Remove backgrounds, inpaint (object removal), or upscale images using AI models.",
        "tags": ["AI"],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": ["file", "action"],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "Image file to process"
                  },
                  "action": {
                    "type": "string",
                    "enum": ["removeBackground", "inpaint", "upscale"],
                    "description": "AI operation to perform"
                  },
                  "maskData": {
                    "type": "string",
                    "format": "binary",
                    "description": "Mask image for inpaint action (required for inpaint)"
                  },
                  "options": {
                    "type": "string",
                    "description": "JSON string. removeBackground: {\"format\":\"png\"}"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "AI-processed image",
            "content": {
              "image/*": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "202": {
            "description": "Job queued for async processing",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/QueuedJob" }
              }
            }
          },
          "400": { "description": "Invalid action, missing file, or missing mask for inpaint" },
          "413": { "description": "File exceeds 10MB limit" },
          "429": { "description": "Rate limit exceeded or queue full" }
        }
      }
    },
    "/api/queue": {
      "get": {
        "operationId": "getJobStatus",
        "summary": "Poll job status",
        "description": "Check the status and queue position of an async processing job.",
        "tags": ["Queue"],
        "parameters": [
          {
            "name": "jobId",
            "in": "query",
            "required": true,
            "schema": { "type": "string" },
            "description": "Job ID from the 202 response"
          },
          {
            "name": "jobToken",
            "in": "query",
            "required": true,
            "schema": { "type": "string" },
            "description": "Job token for authorization"
          }
        ],
        "responses": {
          "200": {
            "description": "Job status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": ["pending", "processing", "done", "error"]
                    },
                    "position": { "type": "integer" },
                    "estimatedWaitMs": { "type": "integer" },
                    "error": { "type": "string" }
                  }
                }
              }
            }
          },
          "400": { "description": "Missing jobId or jobToken" },
          "403": { "description": "Invalid job token" },
          "404": { "description": "Job not found" }
        }
      },
      "delete": {
        "operationId": "cancelJob",
        "summary": "Cancel a queued job",
        "description": "Cancel a job that hasn't started processing yet.",
        "tags": ["Queue"],
        "parameters": [
          {
            "name": "jobId",
            "in": "query",
            "required": true,
            "schema": { "type": "string" }
          },
          {
            "name": "jobToken",
            "in": "query",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Job cancelled",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "cancelled": { "type": "boolean" }
                  }
                }
              }
            }
          },
          "403": { "description": "Invalid job token" },
          "404": { "description": "Job not found or already completed" }
        }
      }
    },
    "/api/queue/result": {
      "get": {
        "operationId": "getJobResult",
        "summary": "Fetch completed job result",
        "description": "Download the processed file after job completes. One-time fetch — result is purged after retrieval.",
        "tags": ["Queue"],
        "parameters": [
          {
            "name": "jobId",
            "in": "query",
            "required": true,
            "schema": { "type": "string" }
          },
          {
            "name": "jobToken",
            "in": "query",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Processed file binary",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "202": { "description": "Job not ready yet — poll /api/queue first" },
          "403": { "description": "Invalid job token" },
          "404": { "description": "Job not found" },
          "410": { "description": "Result expired or already fetched" }
        }
      }
    },
    "/api/health": {
      "get": {
        "operationId": "healthCheck",
        "summary": "Health check",
        "description": "Returns server health status.",
        "tags": ["System"],
        "responses": {
          "200": {
            "description": "Server is healthy",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": { "type": "string", "example": "ok" }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "QueuedJob": {
        "type": "object",
        "properties": {
          "jobId": {
            "type": "string",
            "description": "Unique job identifier"
          },
          "jobToken": {
            "type": "string",
            "description": "Authorization token for this job"
          },
          "position": {
            "type": "integer",
            "description": "Current position in queue"
          },
          "estimatedWaitMs": {
            "type": "integer",
            "description": "Estimated wait time in milliseconds"
          },
          "pollUrl": {
            "type": "string",
            "description": "URL to poll for job status"
          }
        }
      }
    }
  }
}