{
  "openapi": "3.0.3",
  "info": {
    "title": "epub-translator HTTP API",
    "version": "1.0.0",
    "description": "Upload EPUB or DOCX files to MySQL-backed storage, optionally translate text segments via DeepL or AWS Translate, and download rebuilt packages with a chosen translation language.\n\nDocument API routes require the `epub_translator_session` cookie from the web login flow.\n\n**Runtime configuration (not part of this contract):**\n- Database: `MYSQL_*` env vars (see project README).\n- Translation: `TRANSLATION_PROVIDER` = `deepl` | `aws` | `none`. When `none`, `POST .../translate` returns **501**.\n- Default local server: `dub run` listens on **127.0.0.1:8080** and **::1:8080**; large uploads allowed (256 MiB request body limit, override with `MAX_REQUEST_SIZE_MB`; raise proxy `client_max_body_size` if behind nginx)."
  },
  "servers": [
    {
      "url": "http://127.0.0.1:8080",
      "description": "Default `dub run` bind address (see app HTTPServerSettings)"
    }
  ],
  "tags": [
    { "name": "meta", "description": "Discovery and HTML UI" },
    { "name": "documents", "description": "EPUB/DOCX documents in MySQL" },
    { "name": "openapi", "description": "Machine-readable API description" }
  ],
  "paths": {
    "/": {
      "get": {
        "tags": ["meta"],
        "summary": "Minimal HTML upload page",
        "operationId": "getIndex",
        "responses": {
          "200": {
            "description": "HTML form for upload and link to JSON document list",
            "content": {
              "text/html": {
                "schema": { "type": "string" }
              }
            }
          }
        }
      }
    },
    "/api/documents": {
      "get": {
        "tags": ["documents"],
        "summary": "List stored documents",
        "operationId": "listDocuments",
        "security": [{ "cookieAuth": [] }],
        "responses": {
          "200": {
            "description": "JSON array of document summaries",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": { "$ref": "#/components/schemas/DocumentSummary" }
                }
              }
            }
          },
          "503": {
            "description": "Database not configured or connection failed",
            "content": {
              "text/plain": {
                "schema": { "$ref": "#/components/schemas/PlainError" }
              }
            }
          }
        }
      }
    },
    "/api/documents/upload": {
      "post": {
        "tags": ["documents"],
        "summary": "Upload an EPUB or DOCX file",
        "operationId": "uploadDocument",
        "security": [{ "cookieAuth": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": ["file"],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "EPUB or DOCX file (preferred field name; type from filename extension)"
                  },
                  "formFile": {
                    "type": "string",
                    "format": "binary",
                    "description": "Alternative field name (same semantics as `file`)"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Document stored",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/UploadResponse" }
              }
            }
          },
          "400": {
            "description": "Missing file, invalid EPUB/DOCX, or other client error",
            "content": {
              "text/plain": {
                "schema": { "$ref": "#/components/schemas/PlainError" }
              }
            }
          },
          "503": {
            "description": "Database unavailable",
            "content": {
              "text/plain": {
                "schema": { "$ref": "#/components/schemas/PlainError" }
              }
            }
          }
        }
      }
    },
    "/api/documents/{id}/download": {
      "get": {
        "tags": ["documents"],
        "summary": "Download EPUB or DOCX rebuilt from stored data",
        "operationId": "downloadDocument",
        "security": [{ "cookieAuth": [] }],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": { "type": "integer" },
            "description": "Document id"
          },
          {
            "name": "tlanguage",
            "in": "query",
            "required": false,
            "schema": { "type": "string" },
            "description": "Optional translation language code passed to the package writer (embedded translation layer)"
          },
          {
            "name": "sidebyside",
            "in": "query",
            "required": false,
            "schema": { "type": "string", "enum": ["1", "true"] },
            "description": "EPUB only: when `1` or `true`, each XHTML text segment is emitted as a two-column HTML table: original (left) and translation (right). Requires `tlanguage`. Rejected for DOCX."
          }
        ],
        "responses": {
          "200": {
            "description": "EPUB or DOCX binary (content type matches stored document format)",
            "content": {
              "application/epub+zip": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "application/vnd.openxmlformats-officedocument.wordprocessingml.document": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            },
            "headers": {
              "Content-Disposition": {
                "description": "Attachment with document filename and `.epub` or `.docx` extension",
                "schema": { "type": "string" }
              }
            }
          },
          "404": {
            "description": "Document id not found",
            "content": {
              "text/plain": {
                "schema": { "$ref": "#/components/schemas/PlainError" }
              }
            }
          },
          "400": {
            "description": "Invalid id or rebuild error",
            "content": {
              "text/plain": {
                "schema": { "$ref": "#/components/schemas/PlainError" }
              }
            }
          },
          "503": {
            "description": "Database unavailable",
            "content": {
              "text/plain": {
                "schema": { "$ref": "#/components/schemas/PlainError" }
              }
            }
          }
        }
      }
    },
    "/api/documents/{id}/translate": {
      "post": {
        "tags": ["documents"],
        "summary": "Translate missing text segments and persist translations",
        "description": "Requires an active translation provider (`TRANSLATION_PROVIDER` not `none`). Pass languages in the query string or, for `POST` with `application/x-www-form-urlencoded` / `multipart/form-data`, use the same names in the form body. Several aliases are accepted (first non-empty wins per side). Existing non-empty target translations are skipped by default; pass `force=1` to overwrite them.",
        "operationId": "translateDocument",
        "security": [{ "cookieAuth": [] }],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": { "type": "integer" },
            "description": "Document id"
          },
          {
            "name": "slang",
            "in": "query",
            "required": false,
            "schema": { "type": "string" },
            "description": "Source language (alias 1)"
          },
          {
            "name": "olanguage",
            "in": "query",
            "required": false,
            "schema": { "type": "string" },
            "description": "Source language (alias 2)"
          },
          {
            "name": "source_lang",
            "in": "query",
            "required": false,
            "schema": { "type": "string" },
            "description": "Source language (alias 3)"
          },
          {
            "name": "tlang",
            "in": "query",
            "required": false,
            "schema": { "type": "string" },
            "description": "Target language (alias 1)"
          },
          {
            "name": "tlanguage",
            "in": "query",
            "required": false,
            "schema": { "type": "string" },
            "description": "Target language (alias 2)"
          },
          {
            "name": "target_lang",
            "in": "query",
            "required": false,
            "schema": { "type": "string" },
            "description": "Target language (alias 3)"
          },
          {
            "name": "force",
            "in": "query",
            "required": false,
            "schema": { "type": "boolean" },
            "description": "When true, retranslate and overwrite existing non-empty target translations. Aliases: `retranslate`, `force_retranslate`, `overwrite`."
          }
        ],
        "responses": {
          "200": {
            "description": "Translation rows written for the target language",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/TranslateResponse" }
              }
            }
          },
          "501": {
            "description": "Translation disabled (`TRANSLATION_PROVIDER=none`)",
            "content": {
              "text/plain": {
                "schema": { "$ref": "#/components/schemas/PlainError" }
              }
            }
          },
          "404": {
            "description": "Document id not found",
            "content": {
              "text/plain": {
                "schema": { "$ref": "#/components/schemas/PlainError" }
              }
            }
          },
          "400": {
            "description": "Missing language parameters or provider error",
            "content": {
              "text/plain": {
                "schema": { "$ref": "#/components/schemas/PlainError" }
              }
            }
          },
          "503": {
            "description": "Database unavailable",
            "content": {
              "text/plain": {
                "schema": { "$ref": "#/components/schemas/PlainError" }
              }
            }
          }
        }
      }
    },
    "/api/documents/{id}/import-translation": {
      "post": {
        "tags": ["documents"],
        "summary": "Import translations from a matching translated EPUB",
        "description": "Parses an uploaded EPUB or DOCX and copies only its text segments into the existing document's target-language translation layer. The uploaded file must match the stored document's segmented file paths, markup segments, and text segment sequence. On the first mismatch, the request fails without writing any rows and returns the mismatch location.",
        "operationId": "importDocumentTranslation",
        "security": [{ "cookieAuth": [] }],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": { "type": "integer" },
            "description": "Existing document id"
          },
          {
            "name": "target_lang",
            "in": "query",
            "required": false,
            "schema": { "type": "string" },
            "description": "Target language for imported text. Aliases: `tlang`, `tlanguage`, `lang`."
          },
          {
            "name": "overwrite",
            "in": "query",
            "required": false,
            "schema": { "type": "boolean" },
            "description": "When true, overwrite existing non-empty target translations. Aliases: `force`, `retranslate`, `force_retranslate`."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": ["file"],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "Translated EPUB file with matching markup"
                  },
                  "target_lang": {
                    "type": "string",
                    "description": "Target language, accepted in form body as well as query"
                  },
                  "overwrite": {
                    "type": "boolean",
                    "description": "Overwrite existing translations, accepted in form body as well as query"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Translations imported",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ImportTranslationResponse" }
              }
            }
          },
          "404": {
            "description": "Document id not found",
            "content": {
              "text/plain": {
                "schema": { "$ref": "#/components/schemas/PlainError" }
              }
            }
          },
          "400": {
            "description": "Missing language, invalid EPUB, or structural mismatch",
            "content": {
              "text/plain": {
                "schema": { "$ref": "#/components/schemas/PlainError" }
              }
            }
          },
          "503": {
            "description": "Database unavailable",
            "content": {
              "text/plain": {
                "schema": { "$ref": "#/components/schemas/PlainError" }
              }
            }
          }
        }
      }
    },
    "/api/documents/{id}": {
      "delete": {
        "tags": ["documents"],
        "summary": "Delete a document and related rows",
        "operationId": "deleteDocument",
        "security": [{ "cookieAuth": [] }],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": { "type": "integer" },
            "description": "Document id"
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted (empty body)"
          },
          "400": {
            "description": "Invalid id or delete failed",
            "content": {
              "text/plain": {
                "schema": { "$ref": "#/components/schemas/PlainError" }
              }
            }
          },
          "503": {
            "description": "Database unavailable",
            "content": {
              "text/plain": {
                "schema": { "$ref": "#/components/schemas/PlainError" }
              }
            }
          }
        }
      }
    },
    "/openapi.json": {
      "get": {
        "tags": ["openapi"],
        "summary": "OpenAPI 3.0 document (JSON)",
        "operationId": "getOpenApiJson",
        "responses": {
          "200": {
            "description": "OpenAPI specification",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "OpenAPI 3.0 root object"
                }
              }
            }
          }
        }
      }
    },
    "/openapi.js": {
      "get": {
        "tags": ["openapi"],
        "summary": "OpenAPI 3.0 as ES module default export",
        "description": "JavaScript module body: `export default <OpenAPI JSON object literal>;` suitable for bundlers or dynamic `import()`.",
        "operationId": "getOpenApiJs",
        "responses": {
          "200": {
            "description": "OpenAPI specification wrapped for JS",
            "content": {
              "application/javascript": {
                "schema": { "type": "string" }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "cookieAuth": {
        "type": "apiKey",
        "in": "cookie",
        "name": "epub_translator_session"
      }
    },
    "schemas": {
      "DocumentSummary": {
        "type": "object",
        "required": ["documentId", "name", "format"],
        "properties": {
          "documentId": { "type": "integer" },
          "name": { "type": "string" },
          "format": { "type": "string", "enum": ["epub", "docx"] }
        }
      },
      "UploadResponse": {
        "type": "object",
        "required": ["documentId", "name", "format"],
        "properties": {
          "documentId": { "type": "integer" },
          "name": { "type": "string" },
          "format": { "type": "string", "enum": ["epub", "docx"] }
        }
      },
      "TranslateResponse": {
        "type": "object",
        "required": ["documentId", "sourceLang", "targetLang", "force", "totalSegments", "translatedSegments", "skippedExistingSegments", "skippedEmptySegments"],
        "properties": {
          "documentId": { "type": "integer" },
          "sourceLang": { "type": "string" },
          "targetLang": { "type": "string" },
          "force": { "type": "boolean" },
          "totalSegments": { "type": "integer" },
          "translatedSegments": { "type": "integer" },
          "skippedExistingSegments": { "type": "integer" },
          "skippedEmptySegments": { "type": "integer" }
        }
      },
      "ImportTranslationResponse": {
        "type": "object",
        "required": ["documentId", "targetLang", "overwrite", "totalSegments", "importedSegments", "skippedExistingSegments"],
        "properties": {
          "documentId": { "type": "integer" },
          "targetLang": { "type": "string" },
          "overwrite": { "type": "boolean" },
          "totalSegments": { "type": "integer" },
          "importedSegments": { "type": "integer" },
          "skippedExistingSegments": { "type": "integer" }
        }
      },
      "PlainError": {
        "type": "string",
        "description": "Plain-text error message"
      }
    }
  }
}
