{"openapi":"3.1.0","info":{"title":"Mail Merge API v2 v2.0","description":"Mail Merge API v2.0 - Next Generation Document Generation\n\n**Version 2 Features:**\n- Enhanced security with company isolation\n- Async document generation\n- Multiple table support\n- PDF/DOCX output formats\n\n**Note:** Legacy v1 Flask API available at `/api/v1` (separate service)","version":"2.0.0"},"paths":{"/api/v2/auth/register":{"post":{"tags":["Authentication"],"summary":"Register","description":"Register a new company with an admin user.\n\nThis creates:\n1. A new company\n2. A company admin user\n\nReturns access and refresh tokens.","operationId":"register_api_v2_auth_register_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v2/auth/login":{"post":{"tags":["Authentication"],"summary":"Login","description":"Login with email and password.\n\nReturns access and refresh tokens.","operationId":"login_api_v2_auth_login_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v2/auth/refresh":{"post":{"tags":["Authentication"],"summary":"Refresh Access Token","description":"Refresh access token using a valid refresh token.\n\nThe refresh token must be valid and not expired.\nReturns a new access token with the same claims.","operationId":"refresh_access_token_api_v2_auth_refresh_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenRefreshRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenRefreshResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v2/templates":{"post":{"tags":["Templates"],"summary":"Upload Template","description":"Upload a new DOCX template.\n\nThe template file will be:\n1. Validated for allowed extensions\n2. Stored in company-specific directory\n3. Analyzed for merge fields\n4. Registered in database","operationId":"upload_template_api_v2_templates_post","parameters":[{"name":"Authorization","in":"header","required":false,"schema":{"type":"string","title":"Authorization"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_upload_template_api_v2_templates_post"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["Templates"],"summary":"List Templates","description":"List all templates for the current company.\n\nFilters:\n- template_type: Filter by template type (optional)\n- is_active: Show only active templates (default: True)","operationId":"list_templates_api_v2_templates_get","parameters":[{"name":"template_type","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/TemplateType"},{"type":"null"}],"title":"Template Type"}},{"name":"is_active","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Is Active"}},{"name":"Authorization","in":"header","required":false,"schema":{"type":"string","title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/TemplateResponse"},"title":"Response List Templates Api V2 Templates Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v2/templates/{template_id}":{"get":{"tags":["Templates"],"summary":"Get Template","description":"Get a specific template by ID.\n\nEnforces company isolation - only returns template if it belongs to the current company.","operationId":"get_template_api_v2_templates__template_id__get","parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"integer","title":"Template Id"}},{"name":"Authorization","in":"header","required":false,"schema":{"type":"string","title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Templates"],"summary":"Delete Template","description":"Soft delete a template (marks as inactive).\n\nThe template file is not physically deleted to maintain audit trail.","operationId":"delete_template_api_v2_templates__template_id__delete","parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"integer","title":"Template Id"}},{"name":"Authorization","in":"header","required":false,"schema":{"type":"string","title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v2/documents":{"post":{"tags":["Documents"],"summary":"Generate Document","description":"Generate a document from a template with merge data (V2 API).\n\nThis endpoint:\n1. Validates the template exists and belongs to the company\n2. Performs mail merge with provided data\n3. **Processes HTML content** - converts HTML tags to proper DOCX formatting\n4. Auto-detects image fields by PREFIX (img_, imagen_, foto_, etc.)\n5. Downloads and inserts images at placeholder locations\n6. Cleans up empty image placeholders (for optional images)\n7. Converts to PDF if requested\n8. Stores the document with expiration date\n9. Returns document metadata with download information\n\nSupports:\n- AC-1: Simple field merge\n- AC-2: Individual fields + multiple tables\n- AC-3: DOCX and PDF output formats\n- AC-4: Company-isolated storage\n- AC-IMAGE: Images from public URLs with aspect ratio preservation\n- **AC-HTML: HTML content in fields rendered as formatted DOCX**\n\n## Image Fields (V2 Only) - PREFIX Convention\n\nUse these **prefixes** in your template field names to indicate images:\n\n| Prefix    | Language | Examples |\n|-----------|----------|----------|\n| `img_`    | English  | img_logo, img_asset, img_signature |\n| `imagen_` | Spanish  | imagen_producto, imagen_firma |\n| `image_`  | English  | image_header, image_footer |\n| `foto_`   | Spanish  | foto_producto, foto_perfil |\n| `photo_`  | English  | photo_profile, photo_product |\n| `pic_`    | Short    | pic_logo, pic_banner |\n\nTemplate example:\n```\n{{cliente_nombre}}\n{{img_logo_empresa}}      ← Image field (has img_ prefix)\n{{imagen_producto}}       ← Image field (has imagen_ prefix)\n{{precio}}\n```\n\nRequest:\n```json\n{\n  \"fields\": {\n    \"cliente_nombre\": \"Acme Corp\",\n    \"img_logo_empresa\": \"https://example.com/logo.png\",\n    \"imagen_producto\": \"https://example.com/product.jpg\",\n    \"precio\": \"$15,000\"\n  }\n}\n```\n\n### Optional Images (Quote Templates)\n\nFor templates where images may or may not appear:\n- Send empty string `\"\"`, `null`, or `\"N/A\"` for missing images\n- The placeholder will be cleanly removed from the document\n\n```json\n{\n  \"fields\": {\n    \"numero_cotizacion\": \"COT-2025-001\",\n    \"img_activo\": \"https://example.com/asset.jpg\",\n    \"img_opcional\": \"\"\n  }\n}\n```\n\n### Custom Dimensions (explicit images dict)\n\n```json\n{\n  \"images\": {\n    \"logo\": {\n      \"url\": \"https://example.com/logo.png\",\n      \"max_width_inches\": 2.0,\n      \"max_height_inches\": 1.5\n    }\n  }\n}\n```\n\nImages are scaled to maintain aspect ratio (no distortion).\n\n## HTML Content Support (V2 Only)\n\nFields can contain HTML that will be rendered as proper Word formatting:\n\n### Ordered Lists with Type Attribute\n\n```html\n<ol type=\"a\"><li>First item</li><li>Second item</li></ol>\n```\n\n| Type | Output |\n|------|--------|\n| `1` (default) | 1. 2. 3. |\n| `a` | a. b. c. |\n| `A` | A. B. C. |\n| `i` | i. ii. iii. |\n| `I` | I. II. III. |\n\n### Other Supported HTML Elements\n\n- **Lists**: `<ul>`, `<ol>`, `<li>` (with nesting support)\n- **Tables**: `<table>`, `<tr>`, `<td>`, `<th>`\n- **Formatting**: `<b>`, `<strong>`, `<i>`, `<em>`, `<u>`, `<s>`\n- **Structure**: `<p>`, `<br>`, `<h1>`-`<h6>`, `<hr>`\n- **Other**: `<a>` (links), `<blockquote>`, `<pre>`, `<code>`\n\n### Example Request with HTML\n\n```json\n{\n  \"fields\": {\n    \"cliente\": \"Acme Corp\",\n    \"modo_pago\": \"<ol type=\\\"a\\\"><li>Anticipo: $64,224.14</li><li>Gestoría: $9,444.82</li></ol>\"\n  }\n}\n```","operationId":"generate_document_api_v2_documents_post","parameters":[{"name":"Authorization","in":"header","required":false,"schema":{"type":"string","title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocumentGenerateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocumentResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["Documents"],"summary":"List Documents","description":"List generated documents for the current company.\n\nFilters:\n- template_id: Filter by template (optional)\n- ref_id: Filter by reference ID (optional)\n- status: Filter by status (optional)\n- limit: Maximum number of results (default: 100, max: 1000)\n- offset: Pagination offset (default: 0)","operationId":"list_documents_api_v2_documents_get","parameters":[{"name":"template_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Template Id"}},{"name":"ref_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ref Id"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/DocumentStatus"},{"type":"null"}],"title":"Status"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}},{"name":"Authorization","in":"header","required":false,"schema":{"type":"string","title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/DocumentResponse"},"title":"Response List Documents Api V2 Documents Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v2/documents/{document_id}":{"get":{"tags":["Documents"],"summary":"Get Document","description":"Get a specific document by ID.\n\nEnforces company isolation - only returns document if it belongs to the current company.","operationId":"get_document_api_v2_documents__document_id__get","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"integer","title":"Document Id"}},{"name":"Authorization","in":"header","required":false,"schema":{"type":"string","title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocumentResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v2/documents/{document_id}/status":{"get":{"tags":["Documents"],"summary":"Get Document Status","description":"Get the status of a document generation.\n\nUseful for polling when using async task processing.","operationId":"get_document_status_api_v2_documents__document_id__status_get","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"integer","title":"Document Id"}},{"name":"Authorization","in":"header","required":false,"schema":{"type":"string","title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocumentStatusResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v2/documents/{document_id}/download":{"get":{"tags":["Documents"],"summary":"Download Document","description":"Download a generated document.\n\nReturns the PDF or DOCX file with appropriate headers for download.\nEnforces company isolation - only returns document if it belongs to the current company.","operationId":"download_document_api_v2_documents__document_id__download_get","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"integer","title":"Document Id"}},{"name":"Authorization","in":"header","required":false,"schema":{"type":"string","title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v2/users":{"post":{"tags":["Users"],"summary":"Create User","description":"Create a new user in the current company.\n\nOnly company admins can create users.","operationId":"create_user_api_v2_users_post","parameters":[{"name":"Authorization","in":"header","required":false,"schema":{"type":"string","title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["Users"],"summary":"List Users","description":"List all users in the current company.\n\nFilters:\n- is_active: Filter by active status (optional)\n- role: Filter by role (optional)","operationId":"list_users_api_v2_users_get","parameters":[{"name":"is_active","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active"}},{"name":"role","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/UserRole"},{"type":"null"}],"title":"Role"}},{"name":"Authorization","in":"header","required":false,"schema":{"type":"string","title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/UserResponse"},"title":"Response List Users Api V2 Users Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v2/users/{user_id}":{"get":{"tags":["Users"],"summary":"Get User","description":"Get a specific user by ID.\n\nEnforces company isolation.","operationId":"get_user_api_v2_users__user_id__get","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"integer","title":"User Id"}},{"name":"Authorization","in":"header","required":false,"schema":{"type":"string","title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["Users"],"summary":"Update User","description":"Update a user.\n\nOnly company admins can update other users.\nUsers can update their own profile.","operationId":"update_user_api_v2_users__user_id__patch","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"integer","title":"User Id"}},{"name":"Authorization","in":"header","required":false,"schema":{"type":"string","title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Users"],"summary":"Delete User","description":"Soft delete a user (marks as inactive).\n\nOnly company admins can delete users.\nUsers cannot delete themselves.","operationId":"delete_user_api_v2_users__user_id__delete","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"integer","title":"User Id"}},{"name":"Authorization","in":"header","required":false,"schema":{"type":"string","title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v2/pdf/merge":{"post":{"tags":["PDF Operations"],"summary":"Merge Documents from ZIP into PDF","description":"Upload a **ZIP file** containing documents/images and receive a single merged PDF.\n\nThis is the recommended approach for merging multiple files (2 to 1000+).\nFiles inside the ZIP are merged in **alphabetical order** by filename.\n\n## How to Use\n\n1. Create a ZIP file with all documents you want to merge\n2. Name files with prefixes for ordering (e.g., `01_cover.pdf`, `02_intro.pdf`, `03_chapter1.pdf`)\n3. Upload the ZIP file\n4. Receive merged PDF\n\n## Supported Formats (inside ZIP)\n\n| Category | Formats |\n|----------|---------|\n| **Documents** | PDF, XPS, OXPS, EPUB, MOBI, FB2 |\n| **Images** | PNG, JPEG, GIF, BMP, TIFF, WebP, JPEG2000 |\n| **Vector** | SVG |\n| **Comics** | CBZ |\n\n## Limits\n\n- **Max ZIP size**: 1GB (compressed)\n- **Max uncompressed**: 2GB\n- **Max files**: 1000 files per ZIP\n- **Min files**: 2 supported files required\n\n## Automatic Features\n\n- ✅ Auto-detection of file types from content\n- ✅ Automatic repair of corrupt files\n- ✅ Page-by-page recovery for damaged documents\n- ✅ Image fallback for unreadable pages\n- ✅ Unsupported files are skipped (with warning)\n\n## Example\n\n```bash\n# Create ZIP with files to merge\nzip documents.zip 01_cover.pdf 02_contract.pdf 03_signature.jpg 04_appendix.png\n\n# Upload and merge\ncurl -X POST \"/api/v2/pdf/merge\" \\\n  -H \"Authorization: Bearer <token>\" \\\n  -F \"file=@documents.zip\" \\\n  -F \"output_filename=merged_contract\" \\\n  --output merged_contract.pdf\n```\n\n## Response Headers\n\n- `X-Page-Count`: Total pages in merged PDF\n- `X-Source-Files-Count`: Number of files successfully merged\n- `X-Formats-Processed`: File formats that were merged (e.g., \"pdf, jpeg, png\")\n- `X-Skipped-Files-Count`: Files that were skipped (unsupported)","operationId":"merge_from_zip_api_v2_pdf_merge_post","parameters":[{"name":"strict_mode","in":"query","required":false,"schema":{"type":"boolean","description":"If true, fail if ANY file inside ZIP is invalid","default":false,"title":"Strict Mode"},"description":"If true, fail if ANY file inside ZIP is invalid"},{"name":"detailed","in":"query","required":false,"schema":{"type":"boolean","description":"If true, return JSON with detailed merge info instead of PDF","default":false,"title":"Detailed"},"description":"If true, return JSON with detailed merge info instead of PDF"},{"name":"Authorization","in":"header","required":false,"schema":{"type":"string","title":"Authorization"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_merge_from_zip_api_v2_pdf_merge_post"}}}},"responses":{"200":{"description":"Merged PDF file","content":{"application/pdf":{}}},"400":{"description":"Invalid ZIP or insufficient valid files","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PDFMergeErrorResponse"}}}},"413":{"description":"ZIP file size exceeds limit"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v2/pdf/formats":{"get":{"tags":["PDF Operations"],"summary":"Get Supported File Formats","description":"Returns a list of all supported file formats for merging.","operationId":"get_supported_formats_api_v2_pdf_formats_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/v2/pdf/validate":{"post":{"tags":["PDF Operations"],"summary":"Validate Document Files","description":"Validate one or more files of any supported format without merging.\n\nReturns detailed information about each file including:\n- Detected file type\n- Whether the file is valid and processable\n- Page count\n- File size\n- Any errors or warnings\n- Whether repair was attempted\n\nUseful for pre-validating files before merge.\n\n**Supports all formats**: PDF, XPS, EPUB, MOBI, FB2, CBZ, SVG, PNG, JPEG, GIF, BMP, TIFF, WebP, JPEG2000, and more.","operationId":"validate_documents_api_v2_pdf_validate_post","parameters":[{"name":"Authorization","in":"header","required":false,"schema":{"type":"string","title":"Authorization"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_validate_documents_api_v2_pdf_validate_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v2/":{"get":{"summary":"Api V2 Root","description":"API v2 root endpoint.","operationId":"api_v2_root_api_v2__get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/v2/ping":{"get":{"summary":"Ping","description":"Ping endpoint for testing.","operationId":"ping_api_v2_ping_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/health":{"get":{"summary":"Health Check","description":"Health check endpoint.","operationId":"health_check_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/":{"get":{"summary":"Root","description":"Root endpoint.","operationId":"root__get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}}},"components":{"schemas":{"Body_merge_from_zip_api_v2_pdf_merge_post":{"properties":{"file":{"type":"string","format":"binary","title":"File","description":"ZIP file containing documents/images to merge"},"output_filename":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Output Filename","description":"Custom filename for output PDF (without .pdf extension)"}},"type":"object","required":["file"],"title":"Body_merge_from_zip_api_v2_pdf_merge_post"},"Body_upload_template_api_v2_templates_post":{"properties":{"file":{"type":"string","format":"binary","title":"File"},"ref_id":{"type":"string","title":"Ref Id"},"name":{"type":"string","title":"Name"},"template_type":{"$ref":"#/components/schemas/TemplateType"}},"type":"object","required":["file","ref_id","name","template_type"],"title":"Body_upload_template_api_v2_templates_post"},"Body_validate_documents_api_v2_pdf_validate_post":{"properties":{"files":{"items":{"type":"string","format":"binary"},"type":"array","title":"Files","description":"Files to validate (any supported format)"}},"type":"object","required":["files"],"title":"Body_validate_documents_api_v2_pdf_validate_post"},"DocumentFormat":{"type":"string","enum":["docx","pdf"],"title":"DocumentFormat","description":"Output document format."},"DocumentGenerateRequest":{"properties":{"template_id":{"type":"integer","title":"Template Id","description":"UUID of the template to use"},"ref_id":{"type":"string","maxLength":100,"minLength":1,"title":"Ref Id","description":"Client reference ID for the document"},"merge_data":{"$ref":"#/components/schemas/MergeDataRequest","description":"Merge data including fields and tables"},"output_format":{"$ref":"#/components/schemas/DocumentFormat","description":"Output format: 'docx' or 'pdf' (AC-3)","default":"pdf"}},"type":"object","required":["template_id","ref_id","merge_data"],"title":"DocumentGenerateRequest","description":"Request to generate a document from a template.\n\nImplements:\n- AC-1: Mail merge from data into template\n- AC-2: Individual fields + multiple tables\n- AC-3: Output format selection (DOCX or PDF)","example":{"merge_data":{"fields":{"contract_date":"2025-01-08","customer_name":"John Doe","total_amount":"$50,000"},"tables":[{"first_field":"service_name","rows":[{"hours":40,"rate":150,"service_name":"Consulting"},{"hours":80,"rate":125,"service_name":"Development"}]}]},"output_format":"pdf","ref_id":"CONTRACT-2025-001","template_id":"550e8400-e29b-41d4-a716-446655440000"}},"DocumentResponse":{"properties":{"id":{"type":"integer","title":"Id"},"company_id":{"type":"integer","title":"Company Id"},"template_id":{"type":"integer","title":"Template Id"},"ref_id":{"type":"string","title":"Ref Id"},"status":{"$ref":"#/components/schemas/DocumentStatus"},"file_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"File Path"},"file_size":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"File Size"},"download_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Download Url"},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"},"task_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Task Id"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"}},"type":"object","required":["id","company_id","template_id","ref_id","status","expires_at","created_at"],"title":"DocumentResponse","description":"Response schema for generated document.","example":{"company_id":"550e8400-e29b-41d4-a716-446655440000","completed_at":"2025-01-08T12:01:23Z","created_at":"2025-01-08T12:00:00Z","download_url":"/api/v1/documents/650e8400-e29b-41d4-a716-446655440000/download","expires_at":"2025-01-15T12:00:00Z","file_path":"/storage/550e8400-e29b-41d4-a716-446655440000/downloads/CONTRACT-2025-001.pdf","file_size":245678,"id":"650e8400-e29b-41d4-a716-446655440000","ref_id":"CONTRACT-2025-001","status":"completed","task_id":"850e8400-e29b-41d4-a716-446655440000","template_id":"750e8400-e29b-41d4-a716-446655440000"}},"DocumentStatus":{"type":"string","enum":["queued","processing","completed","failed"],"title":"DocumentStatus","description":"Document generation status."},"DocumentStatusResponse":{"properties":{"id":{"type":"integer","title":"Id","description":"Document ID"},"status":{"type":"string","title":"Status","description":"Document status"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Creation timestamp"},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"Last update timestamp"}},"type":"object","required":["id","status","created_at","updated_at"],"title":"DocumentStatusResponse","description":"Document generation status response.","example":{"created_at":"2025-01-08T12:00:00Z","id":"550e8400-e29b-41d4-a716-446655440000","status":"completed","updated_at":"2025-01-08T12:00:30Z"}},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"ImageFieldConfig":{"properties":{"url":{"type":"string","title":"Url","description":"Public URL of the image to download","examples":["https://example.com/logo.png"]},"max_width_inches":{"anyOf":[{"type":"number","maximum":11.0,"minimum":0.1},{"type":"null"}],"title":"Max Width Inches","description":"Maximum width in inches (default: 6.0, fits standard letter/A4)","default":6.0},"max_height_inches":{"anyOf":[{"type":"number","maximum":14.0,"minimum":0.1},{"type":"null"}],"title":"Max Height Inches","description":"Maximum height in inches (default: 8.0)","default":8.0},"width_inches":{"anyOf":[{"type":"number","maximum":11.0,"minimum":0.1},{"type":"null"}],"title":"Width Inches","description":"Exact width in inches (overrides max_width if set)"},"height_inches":{"anyOf":[{"type":"number","maximum":14.0,"minimum":0.1},{"type":"null"}],"title":"Height Inches","description":"Exact height in inches (overrides max_height if set)"},"fit_mode":{"$ref":"#/components/schemas/ImageFitMode","description":"How to fit image: 'contain' (default, preserves aspect ratio), 'cover', 'stretch', 'original'","default":"contain"},"position":{"$ref":"#/components/schemas/ImagePosition","description":"Horizontal alignment: 'left', 'center', 'right'","default":"left"},"alt_text":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Alt Text","description":"Alternative text for accessibility"}},"type":"object","required":["url"],"title":"ImageFieldConfig","description":"Configuration for an image field in merge data.\n\nImages are fetched from public URLs and inserted at placeholder\nlocations in the template. Aspect ratio is preserved by default.\n\nTemplate placeholders: {{field_name}}, <<field_name>>, ${field_name}","examples":[{"fit_mode":"contain","max_height_inches":1.5,"max_width_inches":2.0,"url":"https://example.com/company-logo.png"},{"height_inches":1.0,"position":"center","url":"https://example.com/signature.jpg","width_inches":3.0}]},"ImageFitMode":{"type":"string","enum":["contain","cover","stretch","original"],"title":"ImageFitMode","description":"How to fit image into available space."},"ImagePosition":{"type":"string","enum":["left","center","right"],"title":"ImagePosition","description":"Image alignment within container."},"LoginRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email","description":"User email address"},"password":{"type":"string","maxLength":128,"minLength":8,"title":"Password","description":"User password"}},"type":"object","required":["email","password"],"title":"LoginRequest","description":"Login request schema.","example":{"email":"admin@example.com","password":"SecurePassword123!"}},"LoginResponse":{"properties":{"access_token":{"type":"string","title":"Access Token","description":"JWT access token (15 minutes expiry)"},"refresh_token":{"type":"string","title":"Refresh Token","description":"JWT refresh token (7 days expiry)"},"token_type":{"type":"string","title":"Token Type","description":"Token type for Authorization header","default":"bearer"},"user":{"$ref":"#/components/schemas/UserResponse","description":"User details"}},"type":"object","required":["access_token","refresh_token","user"],"title":"LoginResponse","description":"Login response schema with tokens.","example":{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...","token_type":"bearer","user":{"company_id":"550e8400-e29b-41d4-a716-446655440000","email":"admin@example.com","full_name":"John Admin","id":"450e8400-e29b-41d4-a716-446655440000","is_active":true,"role":"company_admin"}}},"MergeDataRequest":{"properties":{"fields":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Fields","description":"Individual merge fields (AC-1). Fields with image prefixes (img_, imagen_, foto_, etc.) are auto-detected as images.","examples":[{"amount":"$50,000","contract_date":"2025-01-08","customer_name":"John Doe","img_company_logo":"https://example.com/logo.png","img_signature":"https://example.com/signature.jpg"}]},"tables":{"anyOf":[{"items":{"$ref":"#/components/schemas/TableMergeData"},"type":"array","maxItems":50},{"type":"null"}],"title":"Tables","description":"Table data for repeating rows (AC-2)"},"images":{"anyOf":[{"additionalProperties":{"anyOf":[{"type":"string"},{"$ref":"#/components/schemas/ImageFieldConfig"}]},"type":"object","maxProperties":20},{"type":"null"}],"title":"Images","description":"Explicit image fields with URLs (AC-IMAGE). Use for custom dimensions. Key is placeholder name, value is URL string or ImageFieldConfig.","examples":[{"company_logo":"https://example.com/logo.png","signature":{"fit_mode":"contain","max_height_inches":1.0,"max_width_inches":2.0,"url":"https://example.com/signature.jpg"}}]}},"type":"object","title":"MergeDataRequest","description":"Complete merge data structure for document generation.\n\nImplements AC-1, AC-2, and AC-IMAGE:\n- AC-1: Individual field merge via 'fields' dict\n- AC-2: Multiple tables with repeating rows via 'tables' list\n- AC-IMAGE: Images from URLs inserted at placeholder locations\n\nIMAGE AUTO-DETECTION (V2 API) - PREFIX CONVENTION:\nField names with these prefixes are automatically detected as image fields:\n- img_       (English): img_logo, img_asset, img_signature\n- imagen_    (Spanish): imagen_producto, imagen_firma\n- image_     (English): image_header, image_footer\n- foto_      (Spanish): foto_producto, foto_perfil\n- photo_     (English): photo_profile, photo_product\n- pic_       (Short): pic_logo, pic_banner\n\nThe value should be a public URL (http:// or https://).\nEmpty values (\"\", null, \"N/A\") will remove the placeholder from the document.\n\nThis structure is stored in GeneratedDocumentModel.merge_data (JSONB).","examples":[{"fields":{"contract_date":"2025-01-08","customer_name":"John Doe","total_amount":"$50,000"}},{"tables":[{"first_field":"item_name","rows":[{"item_name":"Product A","price":100,"qty":5},{"item_name":"Product B","price":200,"qty":3}]}]},{"fields":{"customer_name":"ABC Corporation","due_date":"2025-02-08","invoice_date":"2025-01-08","invoice_number":"INV-2025-001"},"tables":[{"first_field":"item_description","rows":[{"amount":6000,"hours":40,"item_description":"Consulting Services","rate":150},{"amount":10000,"hours":80,"item_description":"Development Work","rate":125}]},{"first_field":"expense_type","rows":[{"amount":500,"expense_type":"Travel"},{"amount":1200,"expense_type":"Materials"}]}]},{"fields":{"cliente_nombre":"Acme Corporation","fecha":"2025-01-26","imagen_producto":"https://example.com/product.jpg","img_firma":"https://example.com/signature.jpg","img_logo_empresa":"https://example.com/logo.png","numero_contrato":"CTR-2025-001"}},{"fields":{"cliente":"Beta Inc","descripcion_activo":"Bomba Industrial X500","foto_opcional":"","imagen_activo":"https://example.com/pump.jpg","numero_cotizacion":"COT-2025-001","precio":"$15,000"}},{"fields":{"client_name":"Gamma LLC","contract_number":"CTR-2025-002"},"images":{"firma":{"max_height_inches":1.0,"max_width_inches":2.5,"url":"https://example.com/signatures/client-sig.jpg"},"logo":{"fit_mode":"contain","max_height_inches":1.5,"max_width_inches":2.0,"url":"https://example.com/logo.png"}}}]},"PDFFileInfo":{"properties":{"filename":{"type":"string","title":"Filename","description":"Original filename of the uploaded file"},"file_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"File Type","description":"Detected file type (pdf, png, jpeg, epub, etc.)"},"page_count":{"type":"integer","title":"Page Count","description":"Number of pages in this file"},"file_size":{"type":"integer","title":"File Size","description":"Size of this file in bytes"},"is_valid":{"type":"boolean","title":"Is Valid","description":"Whether the file was successfully processed"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Error message if the file could not be processed"},"warning":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Warning","description":"Warning message (e.g., file was repaired or converted)"},"was_repaired":{"type":"boolean","title":"Was Repaired","description":"Whether the file was automatically repaired","default":false},"was_converted":{"type":"boolean","title":"Was Converted","description":"Whether the file was converted from another format","default":false}},"type":"object","required":["filename","page_count","file_size","is_valid"],"title":"PDFFileInfo","description":"Information about a single file in the merge.","example":{"file_size":102400,"file_type":"pdf","filename":"contract_part1.pdf","is_valid":true,"page_count":5,"was_converted":false,"was_repaired":false}},"PDFMergeErrorResponse":{"properties":{"success":{"type":"boolean","title":"Success","description":"Always False for error responses","default":false},"error":{"type":"string","title":"Error","description":"Error message describing what went wrong"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail","description":"Additional technical details about the error"},"source_files":{"anyOf":[{"items":{"$ref":"#/components/schemas/PDFFileInfo"},"type":"array"},{"type":"null"}],"title":"Source Files","description":"Information about source files if partial processing occurred"}},"type":"object","required":["error"],"title":"PDFMergeErrorResponse","description":"Error response schema for PDF merge failures.","example":{"detail":"Only 1 valid PDF was provided out of 3 uploaded files","error":"At least 2 valid PDF files are required for merging","source_files":[{"file_size":102400,"filename":"document1.pdf","is_valid":true,"page_count":5},{"error":"Unable to read PDF: file appears to be corrupted","file_size":1024,"filename":"corrupted.pdf","is_valid":false,"page_count":0},{"error":"File is not a valid PDF","file_size":256,"filename":"not_a_pdf.txt","is_valid":false,"page_count":0}],"success":false}},"RegisterRequest":{"properties":{"company_name":{"type":"string","maxLength":100,"minLength":2,"title":"Company Name","description":"Company name"},"email":{"type":"string","format":"email","title":"Email","description":"Admin user email address"},"password":{"type":"string","maxLength":128,"minLength":8,"title":"Password","description":"Admin user password"},"full_name":{"type":"string","maxLength":100,"minLength":2,"title":"Full Name","description":"Admin user full name"}},"type":"object","required":["company_name","email","password","full_name"],"title":"RegisterRequest","description":"Company and admin user registration request.","example":{"company_name":"Acme Corporation","email":"admin@acme.com","full_name":"John Admin","password":"SecurePassword123!"}},"RegisterResponse":{"properties":{"access_token":{"type":"string","title":"Access Token","description":"JWT access token"},"refresh_token":{"type":"string","title":"Refresh Token","description":"JWT refresh token"},"token_type":{"type":"string","title":"Token Type","description":"Token type","default":"bearer"},"user":{"$ref":"#/components/schemas/UserResponse","description":"Created user details"},"company_id":{"type":"integer","title":"Company Id","description":"Created company ID"},"company_name":{"type":"string","title":"Company Name","description":"Company name"}},"type":"object","required":["access_token","refresh_token","user","company_id","company_name"],"title":"RegisterResponse","description":"Registration response with tokens and user info.","example":{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...","company_id":"550e8400-e29b-41d4-a716-446655440000","company_name":"Acme Corporation","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...","token_type":"bearer","user":{"company_id":"550e8400-e29b-41d4-a716-446655440000","email":"admin@acme.com","full_name":"John Admin","id":"450e8400-e29b-41d4-a716-446655440000","is_active":true,"role":"company_admin"}}},"TableMergeData":{"properties":{"first_field":{"type":"string","maxLength":100,"minLength":1,"title":"First Field","description":"First field name in the table (required by docx-mailmerge)","examples":["item_name","product_code","service_description"]},"rows":{"items":{"type":"object"},"type":"array","maxItems":1000,"minItems":0,"title":"Rows","description":"List of row data dictionaries"}},"type":"object","required":["first_field","rows"],"title":"TableMergeData","description":"Table merge configuration.\n\nSupports AC-2: Multiple tables with repeating rows.\nMaps to docx-mailmerge's merge_rows(first_field, rows) method.","example":{"first_field":"item_name","rows":[{"item_name":"Product A","price":100,"quantity":5},{"item_name":"Product B","price":200,"quantity":3}]}},"TemplateResponse":{"properties":{"id":{"type":"integer","title":"Id"},"company_id":{"type":"integer","title":"Company Id"},"ref_id":{"type":"string","title":"Ref Id"},"name":{"type":"string","title":"Name"},"template_type":{"$ref":"#/components/schemas/TemplateType"},"file_path":{"type":"string","title":"File Path"},"original_filename":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Original Filename"},"file_size":{"type":"integer","title":"File Size"},"merge_fields":{"items":{"type":"string"},"type":"array","title":"Merge Fields"},"version":{"type":"integer","title":"Version"},"is_active":{"type":"boolean","title":"Is Active"},"created_by":{"type":"integer","title":"Created By"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","company_id","ref_id","name","template_type","file_path","file_size","merge_fields","version","is_active","created_by","created_at","updated_at"],"title":"TemplateResponse","description":"Response schema for template.","example":{"company_id":"550e8400-e29b-41d4-a716-446655440000","created_at":"2025-01-08T10:00:00Z","created_by":"450e8400-e29b-41d4-a716-446655440000","file_path":"/storage/550e8400-e29b-41d4-a716-446655440000/uploads/templates/750e8400.docx","file_size":45678,"id":"750e8400-e29b-41d4-a716-446655440000","is_active":true,"merge_fields":["customer_name","contract_date","amount","item_name"],"name":"Standard Sales Contract Template","ref_id":"SALES-CONTRACT-2025","template_type":"contract","updated_at":"2025-01-08T10:00:00Z","version":1}},"TemplateType":{"type":"string","enum":["contract","annex"],"title":"TemplateType","description":"Template type enumeration."},"TokenRefreshRequest":{"properties":{"refresh_token":{"type":"string","title":"Refresh Token","description":"Valid refresh token"}},"type":"object","required":["refresh_token"],"title":"TokenRefreshRequest","description":"Request to refresh access token.","example":{"refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}},"TokenRefreshResponse":{"properties":{"access_token":{"type":"string","title":"Access Token","description":"New JWT access token"},"token_type":{"type":"string","title":"Token Type","description":"Token type for Authorization header","default":"bearer"}},"type":"object","required":["access_token"],"title":"TokenRefreshResponse","description":"Response with new access token.","example":{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...","token_type":"bearer"}},"UserCreate":{"properties":{"email":{"type":"string","format":"email","title":"Email","description":"User email address (unique per company)"},"password":{"type":"string","maxLength":128,"minLength":8,"title":"Password","description":"User password"},"full_name":{"type":"string","maxLength":255,"minLength":1,"title":"Full Name","description":"Full name of the user"},"role":{"$ref":"#/components/schemas/UserRole","description":"User role in the system","default":"user"}},"type":"object","required":["email","password","full_name"],"title":"UserCreate","description":"Request schema for user creation.","example":{"email":"john.doe@example.com","full_name":"John Doe","password":"SecurePass123!","role":"user"}},"UserResponse":{"properties":{"id":{"type":"integer","title":"Id"},"company_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Company Id"},"email":{"type":"string","title":"Email"},"full_name":{"type":"string","title":"Full Name"},"role":{"$ref":"#/components/schemas/UserRole"},"is_active":{"type":"boolean","title":"Is Active"},"last_login_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Login At"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","email","full_name","role","is_active","created_at","updated_at"],"title":"UserResponse","description":"Response schema for user.","example":{"company_id":"550e8400-e29b-41d4-a716-446655440000","created_at":"2025-01-01T00:00:00Z","email":"john.doe@example.com","full_name":"John Doe","id":"450e8400-e29b-41d4-a716-446655440000","is_active":true,"last_login_at":"2025-01-08T09:30:00Z","role":"user","updated_at":"2025-01-08T12:00:00Z"}},"UserRole":{"type":"string","enum":["super_admin","company_admin","user"],"title":"UserRole","description":"User role enumeration."},"UserUpdate":{"properties":{"full_name":{"anyOf":[{"type":"string","maxLength":255,"minLength":1},{"type":"null"}],"title":"Full Name"},"password":{"anyOf":[{"type":"string","maxLength":128,"minLength":8},{"type":"null"}],"title":"Password"},"role":{"anyOf":[{"$ref":"#/components/schemas/UserRole"},{"type":"null"}]},"is_active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active"}},"type":"object","title":"UserUpdate","description":"Request schema for user update.","example":{"full_name":"John A. Doe","is_active":true,"role":"company_admin"}},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}}}}