{
  "openapi": "3.1.0",
  "info": {
    "title": "FacturX API — Public endpoints",
    "description": "Public OpenAPI contract for FacturX API: validate, extract, convert, repair, meta and health. Internal dashboard, billing, admin and webhook routes are intentionally excluded.",
    "contact": {
      "name": "Factur-X Validation API",
      "url": "https://github.com/yourusername/facturx-validation-api"
    },
    "license": {
      "name": "MIT"
    },
    "version": "1.0.0"
  },
  "paths": {
    "/api/v1/convert": {
      "post": {
        "tags": [
          "convert"
        ],
        "summary": "Convert PDF to Factur-X PDF/A-3",
        "description": "Attempts to generate a Factur-X PDF/A-3 candidate from a PDF invoice and returns validation evidence.\n\n`/convert` has four public input modes:\n\n- PDF document extraction: send a PDF file and let the service extract invoice data.\n- Full structured source: send `invoice_data`; it is a complete ERP invoice payload and PDF text parsing is skipped.\n- Stateless inline complements / request-context complements: send `invoice_patch`, `seller_context` and/or `buyer_context` with the document. These fields only complete explicitly supported gaps and never silently overwrite visible PDF/XML data.\n- Explicit tenant-scoped persistent profile reuse: send `profile_reuse=explicit` with tenant-scoped `issuer_id`/`seller_profile_id` and/or `client_id`/`buyer_profile_id`. Saved profile identity/version is included in the request identity. A selector without `profile_reuse=explicit` is rejected instead of silently loading a profile from another tenant.\n\nSaved profile misuse fails closed: missing opt-in returns `profile_reuse_requires_explicit_opt_in`, missing selectors for `profile_reuse=explicit` return `profile_reuse_selector_required`, disagreeing selector aliases return `profile_selector_conflict`, unknown tenant-scoped records return `seller_profile_not_found` or `buyer_profile_not_found`, no profile is silently loaded from another tenant, and profile/request-context conflicts return `profile_inline_context_conflict`.\n\n`dry_run=true` is the preparation/diagnostic surface. It accepts PDF uploads and XML uploads for validation diagnostics, but XML dry-run does not package a final Factur-X PDF. Dry-run responses expose Product Truth `requirements`, `fieldProvenance`, `target`, `validationTarget`, `validationReadiness`, `targetPreflight`, and `conversionInputRecipe` so clients can separate the requested target, EN16931 base readiness, France CTC / FNFE-MPE v1.3.1 blockers, and PA lifecycle status. A successful technical validation or conversion is not PA acceptance; `paAcceptance` stays `not_submitted` until a real PA lifecycle event exists.\n\nUse `validation_target=en16931` for a Factur-X EN16931 conversion. Use `validation_target=france_2026` for EN16931 plus the available France 2026 / FNFE-MPE checks. The response always includes `target.requested`, `target.executed`, `target.status`, `target.missing_inputs`, and `target.not_run_reason`; `success=true` is never emitted unless the requested target was verified.\n\nFor `validation_target=france_2026`, dry-run also emits `targetPreflight`.\n`targetPreflight.exhaustive=true` means the API has generated/validated a\ncandidate, or has an equivalent deterministic proof, and the supported\nFrance 2026 checklist in `targetPreflight.requiredInputs` and\n`conversionInputRecipe.requiredInputs` is complete for the current API\ncapabilities. If the candidate probe is not exhaustive, clients must display\n`Pré-analyse France 2026 incomplète` and must not present the partial checklist\nas complete. Unsupported target blockers stay visible in\n`targetPreflight.unsupportedBlockers`; final `/convert` still fails closed\nunless the requested target is verified.\n\nFrance CTC target resolution is shared with `/validate`: explicit `validation_target=france_2026` (or legacy `validation_profile=fr_ctc`) fails clearly if not executable; legacy `auto&jurisdiction=FR` runs France CTC when the runtime is available; `auto&jurisdiction=UNKNOWN` derives France CTC only from seller/buyer `CountryID=FR` evidence; non-FR request jurisdiction with FR document evidence is reported as a `validationTarget` conflict rather than hidden as either France valid or France CTC blockers. Legacy target controls still live in the `options` JSON for `/convert`: `validation_profile`, `jurisdiction`, `require_profile`, and `message_type`.\n\nCurrent France 2026 / France CTC inline complements:\n\n- BT-26: `invoice_patch.preceding_invoice.issue_date`, with optional matching `preceding_invoice.reference` for DI 0414-style credit-note corrections when BT-25 is already visible/proven.\n- BT-84: `invoice_patch.payment.iban` for a missing seller payment account on a document-derived conversion. It is a partial overlay, not a full `invoice_data` source.\n- BT-34: `seller_context.electronic_address` with `value` (max 125 chars) and alphanumeric `scheme_id` (2-20 chars).\n- BT-49: `buyer_context.electronic_address` with `value` (max 125 chars) and alphanumeric `scheme_id` (2-20 chars).\n- BT-22: `seller_context.document_notes[]` (canonical recipe path) or `buyer_context.document_notes[]` (alternate accepted path) with required `subject_code` PMT, PMD or AAB and non-empty `content` (max 500 chars). Untyped notes are rejected.\n\nExample inline context JSON:\n```json\n{\n  \"invoice_patch\": {\n    \"preceding_invoice\": {\"issue_date\": \"2025-08-31\"},\n    \"payment\": {\"iban\": \"FR7630006000011234567890189\"}\n  },\n  \"seller_context\": {\n    \"electronic_address\": {\"value\": \"12345678900015\", \"scheme_id\": \"0225\"},\n    \"document_notes\": [\n      {\"subject_code\": \"PMT\", \"content\": \"Indemnite forfaitaire de recouvrement de 40 EUR due en cas de retard de paiement\"}\n    ]\n  },\n  \"buyer_context\": {\n    \"electronic_address\": {\"value\": \"98765432100011\", \"scheme_id\": \"0225\"},\n    \"document_notes\": [\n      {\"subject_code\": \"AAB\", \"content\": \"Aucun escompte pour paiement anticipe\"}\n    ]\n  }\n}\n```\n\nFrance 2026 dry-run/final replay pattern: run the dry-run first, copy the\nexact `conversionInputRecipe.examplePayload` roots from that response, and\nreplay them only when `targetPreflight.exhaustive=true` and\n`targetPreflight.unsupportedBlockers` is empty. If the preflight is incomplete\nor unsupported blockers remain, treat the payload as diagnostic-only and expose\nthose blockers instead of launching final `/convert`.\n```bash\ncurl -X POST \"$API/api/v1/convert\"   -H \"Authorization: Bearer $FACTURX_API_KEY\"   -F \"dry_run=true\"   -F \"validation_target=france_2026\"   -F \"file=@./facture.pdf\"\n\ncurl -X POST \"$API/api/v1/convert\"   -H \"Authorization: Bearer $FACTURX_API_KEY\"   -H \"Idempotency-Key: facture-2026-france-001\"   -F \"validation_target=france_2026\"   -F \"file=@./facture.pdf\"   -F 'invoice_patch={\"payment\":{\"iban\":\"FR7630006000011234567890189\"}}'   -F 'seller_context={\"electronic_address\":{\"value\":\"12345678900015\",\"scheme_id\":\"0225\"},\"document_notes\":[{\"subject_code\":\"PMT\",\"content\":\"Indemnite forfaitaire de recouvrement de 40 EUR due en cas de retard de paiement\"},{\"subject_code\":\"PMD\",\"content\":\"Penalites de retard exigibles selon les conditions contractuelles\"},{\"subject_code\":\"AAB\",\"content\":\"Aucun escompte pour paiement anticipe\"}]}'   -F 'buyer_context={\"electronic_address\":{\"value\":\"98765432100011\",\"scheme_id\":\"0225\"}}'\n```\n\nRequest-context complements are stateless. They do not create or update a persisted FacturX profile. Saved issuer/client profile reuse is opt-in only (`profile_reuse=explicit`) and still rejects conflicts with visible PDF/XML values; when a document cannot be completed with supported request context or saved profile fields, send full `invoice_data` as the fallback structured ERP source.\n\n## Conversion Workflow\n\n1. **Scan Detection**: Determines if PDF is native text or scanned image\n2. **Data Extraction**: Extracts invoice data (text parsing or OCR)\n3. **XML Generation**: Creates candidate Factur-X XML, then validates whether it satisfies EN16931 and requested France CTC targets\n4. **PDF/A-3 Generation**: Embeds XML into PDF/A-3 format\n5. **Validation**: Validates the complete Factur-X document\n\n## Authentication\n\nRequires authentication via:\n- `X-API-Key: <api_key>` header, OR\n- `Authorization: Bearer <jwt>` header\n\n## Idempotency\n\nUse `Idempotency-Key` header to ensure safe retries:\n- Same key + same payload = same response (cached)\n- Same key + different payload = 409 Conflict\n- `validation_target`, `invoice_patch`, `seller_context` and `buyer_context` are included in the request identity.\n\n## Options\n\nControl conversion behavior via the `options` form field (JSON):\n```json\n{\n  \"output_profile\": \"EN16931\",\n  \"lang\": \"en\",\n  \"confidence_mode\": \"strict\",\n  \"seller_hint\": \"FR\",\n  \"buyer_hint\": \"DE\"\n}\n```\n\n### Confidence Mode\n- `strict` (default): Reject OCR results with confidence < 70%\n- `lenient`: Accept low confidence with warning in response\n\n## Response\n\nEvery response carries the target truth contract. Generated deliverables and\n`success=true` are only exposed when `target.status=verified` for the requested\n`validation_target`.\n\nWhen the requested target is verified, the response returns the generated\nFactur-X PDF/A-3 candidate with embedded XML and validation report. PDF is\nbase64-encoded in `pdf` for ephemeral responses or exposed through `pdfUrl`\nwhen persistent storage is enabled. Use the `xml` field for the embedded XML.\n\nWhen `target.status` is not `verified`, `/convert` fails closed with\n`success=false`; generated deliverables are not exposed and `target` explains\nwhether the requested target was blocked, failed, or not run.\nValidation is technical. A France CTC pass does not prove PA lifecycle acceptance.\n\n## Quota Usage\n\n- Every authenticated `/convert` call consumes 1 processed invoice.\n- OCR/cloud fallback budgets are enforced separately and do not change monthly quota.",
        "operationId": "convert_invoice_api_v1_convert_post",
        "parameters": [
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "pattern": "^(en|fr)$"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Response language (en or fr). Falls back to Accept-Language header.",
              "title": "Lang"
            },
            "description": "Response language (en or fr). Falls back to Accept-Language header."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Idempotency key for safe retries (recommended for production use)",
              "title": "Idempotency-Key"
            },
            "description": "Idempotency key for safe retries (recommended for production use)"
          },
          {
            "name": "authorization",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Authorization"
            }
          },
          {
            "name": "x-api-key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "X-Api-Key"
            }
          },
          {
            "name": "refresh_token",
            "in": "cookie",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Refresh Token"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/Body_convert_invoice_api_v1_convert_post"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "PDF converted successfully",
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ConvertResponse-Output"
                    },
                    {
                      "$ref": "#/components/schemas/ConvertDryRunErrorEnvelopeResponse-Output"
                    }
                  ],
                  "title": "Response 200 Convert Invoice Api V1 Convert Post"
                },
                "example": {
                  "success": true,
                  "target": {
                    "requested": "en16931",
                    "executed": "en16931",
                    "status": "verified",
                    "missing_inputs": [],
                    "not_run_reason": null
                  },
                  "result": {
                    "durationMs": 2500,
                    "convertedAt": "2026-01-04T12:00:00+00:00",
                    "targetProfile": "EN16931",
                    "conversionSuccessful": true,
                    "xmlSize": 4096,
                    "xml": "PD94bWwgdmVyc2lvbj0iMS4wIj8+...",
                    "pdf": "JVBERi0xLjQK...",
                    "pdfSize": 102400,
                    "extraction": {
                      "sourceType": "native_text",
                      "pageCount": 1
                    },
                    "validation": {
                      "valid": true,
                      "profile": "EN16931"
                    },
                    "packagingPerformed": true,
                    "processedInvoicesCharged": 1,
                    "remainingInvoices": 999,
                    "validationStatus": "valid",
                    "validationScopeLabel": "Factur-X EN16931 base",
                    "appliedValidationProfiles": [
                      "facturx_en16931_base"
                    ],
                    "skippedValidationProfiles": [
                      {
                        "profile": "fr_ctc",
                        "reason": "feature_flag_disabled"
                      }
                    ],
                    "unsupportedValidationProfiles": [],
                    "validationArtifacts": [
                      {
                        "id": "facturx-1.08-en16931-cii",
                        "family": "facturx",
                        "profile": "facturx_en16931_base",
                        "syntax": "cii",
                        "version": "1.08",
                        "sourceAuthority": "FNFE-MPE",
                        "sha256": "0000000000000000000000000000000000000000000000000000000000000000",
                        "status": "available"
                      }
                    ],
                    "messageType": "invoice",
                    "legalDisclaimer": "Validation technique documentaire. Ne vaut pas acceptation par une Plateforme Agréée ni conformité fiscale finale."
                  }
                }
              }
            }
          },
          "202": {
            "description": "Conversion accepted for async processing",
            "content": {
              "application/json": {
                "example": {
                  "success": false,
                  "target": {
                    "requested": "en16931",
                    "executed": null,
                    "status": "not_run",
                    "missing_inputs": [],
                    "not_run_reason": "async_processing_not_completed"
                  },
                  "result": {
                    "status": "processing",
                    "jobId": "2a65594d-7f60-431c-8e35-139a6eeb4cec"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "examples": {
                  "empty_file": {
                    "value": {
                      "detail": "Empty file"
                    }
                  },
                  "invalid_options": {
                    "value": {
                      "error": "invalid_options_json",
                      "message": "Malformed JSON in options: ..."
                    }
                  },
                  "invoice_patch_with_invoice_data": {
                    "value": {
                      "error": "invalid_invoice_patch",
                      "message": "invoice_patch cannot be combined with full invoice_data."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "content": {
              "application/json": {
                "example": {
                  "error": "authentication_required",
                  "message": "Authentication is required for the convert endpoint."
                }
              }
            }
          },
          "402": {
            "description": "Quota mensuel de factures traitées atteint",
            "content": {
              "application/json": {
                "example": {
                  "error": "quota_exceeded",
                  "message": "Quota mensuel de factures traitées atteint.",
                  "plan": "free",
                  "quota": 100,
                  "period_start": "2026-01-01T00:00:00Z"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency conflict or inline context/document conflict",
            "content": {
              "application/json": {
                "examples": {
                  "conflict": {
                    "value": {
                      "error": "idempotency_conflict",
                      "message": "Idempotency key was already used with a different request payload. Use a new Idempotency-Key for different requests."
                    }
                  },
                  "in_progress": {
                    "value": {
                      "error": "idempotency_in_progress",
                      "message": "Operation with this idempotency key is currently in progress. Wait for the operation to complete before retrying.",
                      "processing_since": "2026-01-04T12:00:00Z"
                    }
                  },
                  "inline_context_conflict": {
                    "value": {
                      "error": "inline_context_conflict",
                      "message": "Inline context conflicts with document data.",
                      "conflicts": [
                        {
                          "field": "seller.electronic_address",
                          "document_value": "12345678900015",
                          "context_value": "99999999900019"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "File too large"
          },
          "415": {
            "description": "Unsupported media type (non-PDF file)"
          },
          "422": {
            "description": "Unprocessable entity",
            "content": {
              "application/json": {
                "examples": {
                  "unextractable": {
                    "value": {
                      "error": "unextractable_pdf",
                      "message": "Unable to extract invoice data from PDF. The PDF may be corrupt, password-protected, or not contain invoice content."
                    }
                  },
                  "low_confidence": {
                    "value": {
                      "error": "ocr_confidence_too_low",
                      "message": "OCR confidence (45.2%) is below threshold (70%). Use confidence_mode='lenient' to accept low-confidence results.",
                      "confidence": 45.2,
                      "threshold": 70
                    }
                  },
                  "insufficient_data": {
                    "value": {
                      "error": "insufficient_data",
                      "message": "Required invoice fields are missing.",
                      "missing_fields": [
                        "seller.name",
                        "invoice_number"
                      ]
                    }
                  },
                  "invalid_format": {
                    "value": {
                      "error": "invalid_data_format",
                      "message": "Invoice data contains invalid format.",
                      "field": "issue_date"
                    }
                  },
                  "invalid_invoice_patch": {
                    "summary": "Invalid inline invoice patch",
                    "value": {
                      "error": "invalid_invoice_patch",
                      "message": "invoice_patch is invalid, for example an impossible date."
                    }
                  },
                  "patch_without_reference": {
                    "summary": "BT-26 provided without an effective prior invoice reference",
                    "value": {
                      "error": "insufficient_data",
                      "message": "invoice_patch.preceding_invoice.issue_date requires a matching effective preceding invoice reference."
                    }
                  },
                  "validation_failed": {
                    "value": {
                      "error": "pdf_validation_failed",
                      "message": "Generated PDF failed compliance validation."
                    }
                  },
                  "validation_profile_unavailable": {
                    "summary": "Explicit validation_profile cannot be applied",
                    "value": {
                      "error": "validation_profile_unavailable",
                      "message": "profile_explicit_but_not_runnable:profile=fr_ctc:reason=runtime_not_implemented"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "example": {
                  "error": "rate_limit_exceeded",
                  "message": "Too many requests. Please try again later.",
                  "retry_after_seconds": 30
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable (OCR or PDF generation)",
            "content": {
              "application/json": {
                "examples": {
                  "ocr_service_unavailable": {
                    "value": {
                      "error": "ocr_service_unavailable",
                      "message": "OCR service is temporarily unavailable. Please try again later.",
                      "retry_after_seconds": 60
                    }
                  },
                  "ocr_quota_exhausted": {
                    "value": {
                      "error": "ocr_quota_exhausted",
                      "message": "OCR fallback is temporarily unavailable on this deployment. Please try again later.",
                      "retry_after_seconds": 300
                    }
                  }
                }
              }
            }
          },
          "504": {
            "description": "Timeout (OCR or PDF generation)",
            "content": {
              "application/json": {
                "example": {
                  "error": "ocr_timeout",
                  "message": "OCR processing timed out. The document may be too large or complex."
                }
              }
            }
          }
        },
        "x-source-files": [
          "app/infrastructure/api/routes/convert.py"
        ],
        "x-route-classification": "public_product_api",
        "x-public-contract": true,
        "x-runtime-openapi-source": "/openapi.json",
        "x-contract-validation-test": "tests/unit/test_public_openapi_contract.py"
      }
    },
    "/api/v1/extract": {
      "post": {
        "tags": [
          "extract"
        ],
        "summary": "Extract Factur-X XML from PDF",
        "description": "Extracts the embedded Factur-X XML from a PDF file. This API endpoint is the public integration scan surface (`/api/v1/extract`); the Web `/scan` flow consumes scan-compatible Product Truth payloads built from the same document evidence.\n\n## Features\n\n1. **XML Extraction**: Extracts the embedded XML attachment from Factur-X PDFs\n2. **Profile Detection**: Automatically detects the Factur-X profile (MINIMUM, BASIC_WL, BASIC, EN16931, EXTENDED)\n3. **Optional Validation**: Validates the extracted XML against XSD/Schematron rules\n4. **Truth boundaries**: distinguishes EN16931 base validation, France CTC / FNFE-MPE v1.3.1 readiness, and PA lifecycle acceptance. A scan or extract response is technical evidence only; PA acceptance requires a real external PA lifecycle event.\n\n## Product Truth relation\n\nThe bare `/api/v1/extract` response returns extraction evidence (`xml`, `xmlSize`, `profile`, optional `validation`) and does not emit Product Truth fields directly. When the Web scan/dry-run product layer wraps this evidence, clients must read Product Truth `requirements`, `fieldProvenance`, `validationReadiness`, and `conversionInputRecipe` instead of treating warnings as missing ERP data. Field provenance uses user-level sources (`document`, `profile`, `request_context`, `conflict`) and may expose parser details such as XML/PDF extraction only as technical metadata. Bare extract/scan does not load saved profiles itself; when `conversionInputRecipe` marks a field `saved-profile-supported`, call `/convert` with `profile_reuse=explicit` and tenant-scoped profile selectors. Default or heuristic profile lookup remains unsupported.\n\n## Authentication\n\nRequires authentication via:\n- `X-API-Key: <api_key>` header, OR\n- `Authorization: Bearer <jwt>` header\n\n## Query Parameters\n\n- `validate=true`: Run full PDF validation in the same invoice-treated call\n- `lang=en|fr`: Language for response messages\n\n## Response\n\nReturns base64-encoded extracted XML with metadata:\n- `xml`: Base64-encoded XML content\n- `xmlSize`: Size of XML in bytes\n- `profile`: Detected Factur-X profile\n- `durationMs`: Extraction time in milliseconds\n- `validation`: Optional validation report (if validate=true)\n\n## Quota Usage\n\n- 1 processed invoice per API call, including with `validate=true`",
        "operationId": "extract_facturx_api_v1_extract_post",
        "parameters": [
          {
            "name": "validate",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "description": "Run validation after extraction in the same processed-invoice call",
              "default": false,
              "title": "Validate"
            },
            "description": "Run validation after extraction in the same processed-invoice call"
          },
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "pattern": "^(en|fr)$"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Response language (en or fr). Falls back to Accept-Language header.",
              "title": "Lang"
            },
            "description": "Response language (en or fr). Falls back to Accept-Language header."
          },
          {
            "name": "authorization",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Authorization"
            }
          },
          {
            "name": "x-api-key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "X-Api-Key"
            }
          },
          {
            "name": "refresh_token",
            "in": "cookie",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Refresh Token"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/Body_extract_facturx_api_v1_extract_post"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "XML extracted successfully",
            "content": {
              "application/json": {
                "schema": {},
                "example": {
                  "xml": "PD94bWwgdmVyc2lvbj0iMS4wIj8+...",
                  "xmlSize": 2048,
                  "profile": "EN16931",
                  "filename": "invoice.pdf",
                  "extractedAt": "2024-01-15T12:00:00Z",
                  "durationMs": 42
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "example": {
                  "detail": "Empty file"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "content": {
              "application/json": {
                "example": {
                  "error": "authentication_required",
                  "message": "Authentication is required for the extract endpoint."
                }
              }
            }
          },
          "402": {
            "description": "Quota mensuel de factures traitées atteint"
          },
          "413": {
            "description": "File too large"
          },
          "415": {
            "description": "Unsupported media type (non-PDF file)"
          },
          "422": {
            "description": "PDF without Factur-X XML",
            "content": {
              "application/json": {
                "example": {
                  "error": "unextractable_pdf",
                  "message": "PDF does not contain embedded Factur-X XML attachment."
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "example": {
                  "error": "rate_limit_exceeded",
                  "message": "Too many requests. Please try again later.",
                  "retry_after_seconds": 30
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable"
          }
        },
        "x-source-files": [
          "app/infrastructure/api/routes/extract.py"
        ],
        "x-route-classification": "public_product_api",
        "x-public-contract": true,
        "x-runtime-openapi-source": "/openapi.json",
        "x-contract-validation-test": "tests/unit/test_public_openapi_contract.py"
      }
    },
    "/api/v1/repair": {
      "post": {
        "tags": [
          "repair"
        ],
        "summary": "Repair Factur-X XML",
        "description": "Repairs a Factur-X XML file to fix common EN16931 validation issues.\n\n## Three-Phase Workflow\n\n1. **Validate BEFORE**: Capture the initial validation state\n2. **Apply Repairs**: Apply deterministic repair rules\n3. **Validate AFTER**: Verify that repairs improved validity\n\n## Repair Rules Applied\n\n- **R001**: Date format correction (YYYY-MM-DD -> YYYYMMDD)\n- **R002**: Decimal format correction (comma -> dot separator)\n- **R005**: Namespace prefix/URI fixes\n- **R006**: Required schemeID attributes\n- **R007**: Currency code casing (eur -> EUR)\n- **R008**: Missing required elements (constraint-gated)\n\n## Authentication\n\nRequires authentication via:\n- `X-API-Key: <api_key>` header, OR\n- `Authorization: Bearer <jwt>` header\n\n## Idempotency\n\nUse `Idempotency-Key` header to ensure safe retries:\n- Same key + same payload = same response (cached)\n- Same key + different payload = 409 Conflict\n\n## Constraints\n\nControl repair behavior via the `constraints` form field (JSON):\n```json\n{\n  \"max_changes_allowed\": 10,\n  \"do_not_modify_amounts\": true,\n  \"do_not_invent_missing_parties\": true,\n  \"target_profile\": \"EN16931\"\n}\n```\n\n## Response\n\nReturns base64-encoded repaired XML with before/after validation reports\nand a diff summary of all changes made.",
        "operationId": "repair_facturx_xml_api_v1_repair_post",
        "parameters": [
          {
            "name": "target_profile",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "pattern": "^(MINIMUM|BASIC_WL|BASIC|EN16931|EXTENDED)$"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Target Factur-X profile for repair. If not specified, auto-detected from XML.",
              "title": "Target Profile"
            },
            "description": "Target Factur-X profile for repair. If not specified, auto-detected from XML."
          },
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "pattern": "^(en|fr)$"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Response language (en or fr). Falls back to Accept-Language header.",
              "title": "Lang"
            },
            "description": "Response language (en or fr). Falls back to Accept-Language header."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Idempotency key for safe retries (recommended for production use)",
              "title": "Idempotency-Key"
            },
            "description": "Idempotency key for safe retries (recommended for production use)"
          },
          {
            "name": "authorization",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Authorization"
            }
          },
          {
            "name": "x-api-key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "X-Api-Key"
            }
          },
          {
            "name": "refresh_token",
            "in": "cookie",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Refresh Token"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/Body_repair_facturx_xml_api_v1_repair_post"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "XML repaired successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RepairResponse"
                },
                "example": {
                  "durationMs": 124,
                  "repaired_xml": "PD94bWwgdmVyc2lvbj0iMS4wIj8+...",
                  "diff_summary": [
                    {
                      "code": "format_corrected",
                      "path": "/rsm:CrossIndustryInvoice/.../ram:IssueDateTime",
                      "message": "Corrected date format from '2024-01-15' to '20240115'",
                      "beforeValue": "2024-01-15",
                      "afterValue": "20240115",
                      "ruleReference": "R001"
                    }
                  ],
                  "validation_before": {
                    "valid": false,
                    "target": {
                      "requested": "en16931",
                      "executed": "en16931",
                      "status": "failed",
                      "missing_inputs": [],
                      "not_run_reason": null
                    },
                    "summary": {
                      "errorCount": 2,
                      "warningCount": 0
                    }
                  },
                  "target": {
                    "requested": "en16931",
                    "executed": "en16931",
                    "status": "verified",
                    "missing_inputs": [],
                    "not_run_reason": null
                  },
                  "repairSuccessful": true,
                  "validation_after": {
                    "valid": true,
                    "target": {
                      "requested": "en16931",
                      "executed": "en16931",
                      "status": "verified",
                      "missing_inputs": [],
                      "not_run_reason": null
                    },
                    "summary": {
                      "errorCount": 0,
                      "warningCount": 0
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "examples": {
                  "empty_file": {
                    "value": {
                      "detail": "Empty file"
                    }
                  },
                  "invalid_constraints": {
                    "value": {
                      "error": "invalid_constraints_json",
                      "message": "Malformed JSON in constraints: ..."
                    }
                  }
                }
              }
            }
          },
          "402": {
            "description": "Quota mensuel de factures traitées atteint"
          },
          "409": {
            "description": "Idempotency conflict",
            "content": {
              "application/json": {
                "example": {
                  "error": "idempotency_conflict",
                  "message": "Idempotency key was already used with a different request payload. Use a new Idempotency-Key for different requests."
                }
              }
            }
          },
          "413": {
            "description": "File too large"
          },
          "415": {
            "description": "Unsupported media type (non-XML file)"
          },
          "422": {
            "description": "Unprocessable entity",
            "content": {
              "application/json": {
                "examples": {
                  "xml_parse_error": {
                    "value": {
                      "error": "xml_parse_error",
                      "message": "XML syntax error: ..."
                    }
                  },
                  "xml_security_error": {
                    "value": {
                      "error": "xml_security_error",
                      "message": "XML security violation: potential XXE attack detected"
                    }
                  },
                  "constraint_exceeded": {
                    "value": {
                      "error": "repair_constraint_exceeded",
                      "message": "Repair would exceed max_changes_allowed limit"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "example": {
                  "error": "rate_limit_exceeded",
                  "message": "Too many requests. Please try again later.",
                  "retry_after_seconds": 30
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable"
          }
        },
        "x-source-files": [
          "app/infrastructure/api/routes/repair.py"
        ],
        "x-route-classification": "public_product_api",
        "x-public-contract": true,
        "x-runtime-openapi-source": "/openapi.json",
        "x-contract-validation-test": "tests/unit/test_public_openapi_contract.py"
      }
    },
    "/api/v1/validate": {
      "post": {
        "tags": [
          "validation"
        ],
        "summary": "Valider une facture Factur-X",
        "description": "Valide une facture électronique au format **Factur-X** (PDF ou XML).\n\n## 🔐 Authentification\n\nCet endpoint supporte trois méthodes d'authentification :\n\n1. **JWT Dashboard** : `Authorization: Bearer <jwt>` (utilisateurs connectés)\n2. **API Key** : `X-API-Key: <key>` ou `Authorization: Bearer <api_key>` (clients externes)\n3. **Refresh Token** : Cookie `refresh_token` (renouvellement automatique)\n\n## 📥 Formats supportés\n\n- **PDF Factur-X** : PDF avec XML embarqué conforme EN16931\n- **XML standalone** : Fichier XML Factur-X seul\n\n## ✅ Validations effectuées\n\n1. **Validation PDF/A-3** : Vérifie la conformité PDF/A-3 (si PDF)\n2. **Validation structure Factur-X** : Vérifie les pièces jointes et métadonnées XMP (si PDF)\n3. **Validation XSD** : Vérifie la structure XML contre le schéma XSD\n4. **Validation Schematron EN16931** : Vérifie les règles métier européennes\n5. **France 2026 / FNFE-MPE** : si demandé par `validation_profile=fr_ctc`, `validation_profile=auto&jurisdiction=FR`, ou par preuve pays document (`CountryID=FR` vendeur/client avec `jurisdiction=UNKNOWN`), applique la cible France CTC quand le runtime est disponible. La version active des artefacts France CTC est FNFE-MPE v1.3.1.\n\nCette validation est technique. Elle ne soumet pas le document à une Plateforme Agréée et ne vaut pas acceptation PA.\n\n## Product Truth et provenance\n\n`/validate` retourne la vérité de validation technique: `issues`, `stages`, `target`, `validationArtifacts`, `validationTarget` et `validationReadiness` séparent la cible demandée, le socle EN16931, la cible France CTC / FNFE-MPE v1.3.1 et l'état `paAcceptance`. Il ne produit pas de `conversionInputRecipe` actionnable pour réparer une facture; les exigences Product Truth, la `fieldProvenance`, les compléments inline et les gaps de réutilisation de profils persistants sont exposés par les surfaces scan/dry-run `/convert`. Si un champ de classification public (`status`, `internalCategory`, `uiBucket`) est présent sur une issue, il doit être explicite et non nul.\n\n## 🎯 Cibles de validation\n\n- `validation_target=en16931` : socle Factur-X / EN16931 uniquement.\n- `validation_target=france_2026` : EN16931 + exigences France 2026 / FNFE-MPE disponibles. Cette cible ne vaut pas soumission ni acceptation Plateforme Agréée.\n- `validation_profile=facturx_en16931_base` : ancien contrôle équivalent à EN16931 uniquement.\n- `validation_profile=auto&jurisdiction=FR` : ancien contrôle qui cible France 2026 quand les artefacts/runtime France CTC sont disponibles.\n- `validation_profile=auto&jurisdiction=UNKNOWN` : cible France 2026 uniquement si les pays vendeur/client extraits du XML prouvent `FR`; les pays inconnus/non-FR restent EN16931 only avec raison explicite.\n- `validation_profile=auto&jurisdiction=EU` avec preuve document FR : expose un conflit `validationTarget` sans exécuter France CTC ni afficher de blockers France CTC.\n- `validation_profile=auto&jurisdiction=FR` avec preuve document non-FR : exécute la cible demandée mais expose un conflit `validationTarget` request/document.\n- Un runtime France CTC indisponible en mode `auto` est rendu comme `validationTarget.frCtc.target=unavailable` ou `runtime_error`, pas comme un blocker document.\n- `validation_profile=fr_ctc` : demande explicite France CTC; retourne 422 si la cible n'est pas exécutable.\n- `legacy=true` : conserve l'ancien format court de réponse; le format par défaut reste le rapport unifié.\n\nLes erreurs de résolution de profil sont classées avant ou remboursées par le wrapper quota, afin de ne pas facturer un échec runtime déterministe de cible non exécutable.\n\n## 📤 Réponses possibles\n\n### Succès (200 OK) - Format unifié (par défaut)\n```json\n{\n  \"valid\": true,\n  \"validatedAt\": \"2025-12-27T12:23:51.676602+00:00\",\n  \"filename\": \"invoice.xml\",\n  \"profile\": \"EN16931\",\n  \"durationMs\": 124,\n  \"summary\": { \"errorCount\": 0, \"warningCount\": 0 },\n  \"target\": {\n    \"requested\": \"en16931\",\n    \"executed\": \"en16931\",\n    \"status\": \"verified\",\n    \"missing_inputs\": [],\n    \"not_run_reason\": null\n  },\n  \"issues\": [],\n  \"stages\": {\n    \"pdfa\": { \"passed\": true, \"skipped\": true, \"skip_reason\": \"XML file - PDF/A validation not applicable\", \"error_count\": 0, \"warning_count\": 0, \"info_count\": 0, \"issues\": [] },\n    \"facturx_pdf\": { \"passed\": true, \"skipped\": true, \"skip_reason\": \"XML file - PDF checks not applicable\", \"error_count\": 0, \"warning_count\": 0, \"info_count\": 0, \"issues\": [] },\n    \"xsd\": { \"passed\": true, \"skipped\": false, \"skip_reason\": null, \"error_count\": 0, \"warning_count\": 0, \"info_count\": 0, \"issues\": [] },\n    \"schematron\": { \"passed\": true, \"skipped\": false, \"skip_reason\": null, \"error_count\": 0, \"warning_count\": 0, \"info_count\": 0, \"issues\": [] }\n  },\n  \"errors\": [],\n  \"warnings\": []\n}\n```\n\n### Succès (200 OK) - Format legacy (?legacy=true)\n```json\n{\n  \"valid\": true,\n  \"errors\": [],\n  \"warnings\": [],\n  \"profile\": \"EN16931\",\n  \"message\": \"Validation completed\",\n  \"target\": {\n    \"requested\": \"en16931\",\n    \"executed\": \"en16931\",\n    \"status\": \"verified\",\n    \"missing_inputs\": [],\n    \"not_run_reason\": null\n  }\n}\n```\n\n### Validation échouée (200 OK avec valid=false)\n```json\n{\n  \"valid\": false,\n  \"validatedAt\": \"2025-12-27T12:23:51.676602+00:00\",\n  \"filename\": \"invoice.xml\",\n  \"profile\": \"EN16931\",\n  \"durationMs\": 124,\n  \"summary\": { \"errorCount\": 1, \"warningCount\": 0 },\n  \"target\": {\n    \"requested\": \"en16931\",\n    \"executed\": \"en16931\",\n    \"status\": \"failed\",\n    \"missing_inputs\": [],\n    \"not_run_reason\": null\n  },\n  \"issues\": [\n    {\n      \"code\": \"XSD_VALIDATION_ERROR\",\n      \"severity\": \"error\",\n      \"stage\": \"xsd\",\n      \"message\": \"Missing required element: InvoiceNumber\",\n      \"line\": 42,\n      \"expected\": \"InvoiceNumber\",\n      \"actual\": null,\n      \"location\": \"line 42\",\n      \"ruleId\": null,\n      \"xpath\": \"/Invoice/ID\"\n    }\n  ],\n  \"stages\": {\n    \"pdfa\": { \"passed\": true, \"skipped\": true, \"skip_reason\": \"XML file - PDF/A validation not applicable\", \"error_count\": 0, \"warning_count\": 0, \"info_count\": 0, \"issues\": [] },\n    \"facturx_pdf\": { \"passed\": true, \"skipped\": true, \"skip_reason\": \"XML file - PDF checks not applicable\", \"error_count\": 0, \"warning_count\": 0, \"info_count\": 0, \"issues\": [] },\n    \"xsd\": { \"passed\": false, \"skipped\": false, \"skip_reason\": null, \"error_count\": 1, \"warning_count\": 0, \"info_count\": 0, \"issues\": [] },\n    \"schematron\": { \"passed\": true, \"skipped\": false, \"skip_reason\": null, \"error_count\": 0, \"warning_count\": 0, \"info_count\": 0, \"issues\": [] }\n  },\n  \"errors\": [],\n  \"warnings\": []\n}\n```\n\n### Erreur fichier vide (400 Bad Request)\n```json\n{\n  \"detail\": \"Empty file\"\n}\n```\n\n## 🌍 Internationalisation\n\nUtilisez le header **Accept-Language** pour obtenir les messages en français:\n- `Accept-Language: en` (par défaut)\n- `Accept-Language: fr`\n\n## 💾 Stockage\n\nLe fichier est stocké temporairement sur R2/stockage local pour traçabilité.",
        "operationId": "validate_facturx_api_v1_validate_post",
        "parameters": [
          {
            "name": "legacy",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "description": "Return the legacy short response format. Default is the unified validation report.",
              "default": false,
              "title": "Legacy"
            },
            "description": "Return the legacy short response format. Default is the unified validation report."
          },
          {
            "name": "enrich",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "description": "Enable Sirene company enrichment for this request.",
              "default": true,
              "title": "Enrich"
            },
            "description": "Enable Sirene company enrichment for this request."
          },
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "pattern": "^(en|fr)$"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Response language (en or fr). Falls back to Accept-Language header if not specified.",
              "title": "Lang"
            },
            "description": "Response language (en or fr). Falls back to Accept-Language header if not specified."
          },
          {
            "name": "validation_profile",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^(auto|facturx_en16931_base|facturx_extended|fr_ctc|chorus_b2g)$",
              "description": "Validation target. Use facturx_en16931_base for EN16931-only. Use auto with jurisdiction=FR for the France 2026 target.",
              "default": "auto",
              "title": "Validation Profile"
            },
            "description": "Validation target. Use facturx_en16931_base for EN16931-only. Use auto with jurisdiction=FR for the France 2026 target."
          },
          {
            "name": "validation_target",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "pattern": "^(en16931|france_2026)$"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Explicit target contract. Use en16931 for Factur-X EN16931, or france_2026 for EN16931 plus available France 2026 / FNFE-MPE checks.",
              "title": "Validation Target"
            },
            "description": "Explicit target contract. Use en16931 for Factur-X EN16931, or france_2026 for EN16931 plus available France 2026 / FNFE-MPE checks."
          },
          {
            "name": "jurisdiction",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^(UNKNOWN|FR|EU)$",
              "description": "Caller-declared jurisdiction. auto + FR derives France CTC when the runtime is available.",
              "default": "UNKNOWN",
              "title": "Jurisdiction"
            },
            "description": "Caller-declared jurisdiction. auto + FR derives France CTC when the runtime is available."
          },
          {
            "name": "require_profile",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "description": "Kept for parity with /convert. Explicit validation_profile values already fail with 422 when unavailable.",
              "default": false,
              "title": "Require Profile"
            },
            "description": "Kept for parity with /convert. Explicit validation_profile values already fail with 422 when unavailable."
          },
          {
            "name": "message_type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^invoice$",
              "description": "Message family to validate. /validate currently supports invoice CII.",
              "default": "invoice",
              "title": "Message Type"
            },
            "description": "Message family to validate. /validate currently supports invoice CII."
          },
          {
            "name": "authorization",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Authorization"
            }
          },
          {
            "name": "x-api-key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "X-Api-Key"
            }
          },
          {
            "name": "refresh_token",
            "in": "cookie",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Refresh Token"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/Body_validate_facturx_api_v1_validate_post"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Validation effectuée (succès ou échec)",
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/UnifiedValidationResponse-Output"
                    },
                    {
                      "$ref": "#/components/schemas/ValidationResponse"
                    }
                  ],
                  "title": "Response Validate Facturx Api V1 Validate Post"
                },
                "examples": {
                  "valid": {
                    "summary": "Facture valide - format unifié par défaut",
                    "value": {
                      "valid": true,
                      "validatedAt": "2026-04-28T10:00:00.000Z",
                      "filename": "invoice.xml",
                      "profile": "EN16931",
                      "durationMs": 124,
                      "summary": {
                        "errorCount": 0,
                        "warningCount": 0
                      },
                      "target": {
                        "requested": "en16931",
                        "executed": "en16931",
                        "status": "verified",
                        "missing_inputs": [],
                        "not_run_reason": null
                      },
                      "issues": [],
                      "stages": {
                        "pdfa": {
                          "passed": true,
                          "skipped": true,
                          "skip_reason": "XML file - PDF/A validation not applicable",
                          "error_count": 0,
                          "warning_count": 0,
                          "info_count": 0,
                          "issues": []
                        },
                        "facturx_pdf": {
                          "passed": true,
                          "skipped": true,
                          "skip_reason": "XML file - PDF checks not applicable",
                          "error_count": 0,
                          "warning_count": 0,
                          "info_count": 0,
                          "issues": []
                        },
                        "xsd": {
                          "passed": true,
                          "skipped": false,
                          "error_count": 0,
                          "warning_count": 0,
                          "info_count": 0,
                          "issues": []
                        },
                        "schematron": {
                          "passed": true,
                          "skipped": false,
                          "error_count": 0,
                          "warning_count": 0,
                          "info_count": 0,
                          "issues": []
                        }
                      },
                      "errors": [],
                      "warnings": []
                    }
                  },
                  "invalid_xsd": {
                    "summary": "Erreur XSD - format unifié par défaut",
                    "value": {
                      "valid": false,
                      "validatedAt": "2026-04-28T10:00:00.000Z",
                      "filename": "invoice.xml",
                      "profile": "EN16931",
                      "durationMs": 124,
                      "summary": {
                        "errorCount": 1,
                        "warningCount": 0
                      },
                      "target": {
                        "requested": "en16931",
                        "executed": "en16931",
                        "status": "failed",
                        "missing_inputs": [],
                        "not_run_reason": null
                      },
                      "issues": [
                        {
                          "code": "XSD_VALIDATION_ERROR",
                          "severity": "error",
                          "stage": "xsd",
                          "message": "Missing required element: InvoiceNumber",
                          "line": 42,
                          "expected": "InvoiceNumber",
                          "location": "line 42",
                          "xpath": "/Invoice/ID"
                        }
                      ],
                      "stages": {
                        "pdfa": {
                          "passed": true,
                          "skipped": true,
                          "skip_reason": "XML file - PDF/A validation not applicable",
                          "error_count": 0,
                          "warning_count": 0,
                          "info_count": 0,
                          "issues": []
                        },
                        "facturx_pdf": {
                          "passed": true,
                          "skipped": true,
                          "skip_reason": "XML file - PDF checks not applicable",
                          "error_count": 0,
                          "warning_count": 0,
                          "info_count": 0,
                          "issues": []
                        },
                        "xsd": {
                          "passed": false,
                          "skipped": false,
                          "error_count": 1,
                          "warning_count": 0,
                          "info_count": 0,
                          "issues": []
                        },
                        "schematron": {
                          "passed": true,
                          "skipped": false,
                          "error_count": 0,
                          "warning_count": 0,
                          "info_count": 0,
                          "issues": []
                        }
                      },
                      "errors": [
                        {
                          "code": "XSD_VALIDATION_ERROR",
                          "severity": "error",
                          "stage": "xsd",
                          "message": "Missing required element: InvoiceNumber",
                          "line": 42,
                          "expected": "InvoiceNumber",
                          "location": "line 42",
                          "xpath": "/Invoice/ID"
                        }
                      ],
                      "warnings": []
                    }
                  },
                  "legacy": {
                    "summary": "Format legacy avec ?legacy=true",
                    "value": {
                      "valid": true,
                      "errors": [],
                      "warnings": [],
                      "profile": "EN16931",
                      "message": "Validation completed",
                      "target": {
                        "requested": "en16931",
                        "executed": "en16931",
                        "status": "verified",
                        "missing_inputs": [],
                        "not_run_reason": null
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Fichier vide ou invalide"
          },
          "402": {
            "description": "Quota mensuel de factures traitées atteint (quota_exceeded)"
          },
          "413": {
            "description": "Fichier trop volumineux (file_too_large)"
          },
          "415": {
            "description": "Type de fichier non supporté (unsupported_media_type)"
          },
          "422": {
            "description": "Cible de validation explicite non exécutable (validation_profile_unavailable)"
          },
          "429": {
            "description": "Rate limit dépassé (rate_limit_exceeded)"
          },
          "500": {
            "description": "Erreur serveur (stockage, traitement)"
          },
          "503": {
            "description": "Validation engine indisponible (service_unavailable)"
          }
        },
        "x-source-files": [
          "app/infrastructure/api/routes/validate.py"
        ],
        "x-route-classification": "public_product_api",
        "x-public-contract": true,
        "x-runtime-openapi-source": "/openapi.json",
        "x-contract-validation-test": "tests/unit/test_public_openapi_contract.py"
      }
    },
    "/health": {
      "get": {
        "summary": "Health",
        "description": "Health check endpoint with database ping + schema drift detection.\n\nReturns:\n    - 200 ``{\"status\": \"ok\", \"db\": \"ok\", \"schema\": \"ok\"}`` when all\n      systems are operational.\n    - 503 ``{\"status\": \"degraded\", \"db\": \"error\"}`` when the DB is\n      unreachable.\n    - 503 ``{\"status\": \"degraded\", \"db\": \"ok\", \"schema\": \"drift\",\n      \"missing\": {<table>: [<col>...]}, \"missing_migrations\": [...]}``\n      when the connection works but D1 is missing canary columns from\n      a migration that the deployed code already depends on.",
        "operationId": "health_health_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        },
        "x-source-files": [
          "app/main.py"
        ],
        "x-route-classification": "public_product_api",
        "x-public-contract": true,
        "x-runtime-openapi-source": "/openapi.json",
        "x-contract-validation-test": "tests/unit/test_public_openapi_contract.py"
      }
    },
    "/meta": {
      "get": {
        "tags": [
          "meta"
        ],
        "summary": "Public metadata (source of truth)",
        "description": "Public, read-only metadata used as a single source of truth for versions, artefacts, limits, and plan quotas.",
        "operationId": "get_meta_meta_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MetaResponse"
                }
              }
            }
          }
        },
        "x-source-files": [
          "app/infrastructure/api/routes/meta.py"
        ],
        "x-route-classification": "public_product_api",
        "x-public-contract": true,
        "x-runtime-openapi-source": "/openapi.json",
        "x-contract-validation-test": "tests/unit/test_public_openapi_contract.py"
      }
    }
  },
  "webhooks": {
    "api_v1_webhooks_stripe": {
      "post": {
        "summary": "POST /api/v1/webhooks/stripe",
        "description": "Stripe webhook handler contract. Signature verification is enforced through the Stripe-Signature header before event processing.",
        "x-source-files": [
          "app/infrastructure/api/routes/webhooks.py"
        ],
        "responses": {
          "default": {
            "description": "Implemented route response; see runtime FastAPI schema or route tests for detailed payload fields."
          }
        },
        "x-handler-path": "/api/v1/webhooks/stripe",
        "x-signature-header": "stripe-signature",
        "x-route-coverage-only": true,
        "x-route-classification": "webhook",
        "x-public-contract": false,
        "x-publication": "internal-route-coverage-only",
        "x-signature-verifier": "verify_and_handle"
      }
    },
    "webhooks_stripe": {
      "post": {
        "summary": "POST /webhooks/stripe",
        "description": "Stripe webhook handler contract. Signature verification is enforced through the Stripe-Signature header before event processing.",
        "x-source-files": [
          "app/infrastructure/api/routes/webhooks.py"
        ],
        "responses": {
          "default": {
            "description": "Implemented route response; see runtime FastAPI schema or route tests for detailed payload fields."
          }
        },
        "x-handler-path": "/webhooks/stripe",
        "x-signature-header": "stripe-signature",
        "x-route-coverage-only": true,
        "x-route-classification": "webhook",
        "x-public-contract": false,
        "x-publication": "internal-route-coverage-only",
        "x-signature-verifier": "verify_and_handle"
      }
    }
  },
  "components": {
    "schemas": {
      "Body_convert_invoice_api_v1_convert_post": {
        "properties": {
          "file": {
            "type": "string",
            "format": "binary",
            "title": "File",
            "description": "PDF file to convert to Factur-X format"
          },
          "options": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Options",
            "description": "JSON object with conversion options (optional). Prefer validation_target (en16931 or france_2026). Legacy target controls remain supported: validation_profile values auto, facturx_en16931_base, facturx_extended, fr_ctc, chorus_b2g; jurisdiction values UNKNOWN, FR, EU; require_profile; message_type=invoice."
          },
          "invoice_data": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Invoice Data",
            "description": "JSON structured invoice data from ERP (optional). When provided, PDF text parsing is skipped."
          },
          "invoice_patch": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Invoice Patch",
            "description": "JSON invoice-specific patch applied after PDF extraction. Supports preceding_invoice.issue_date (BT-26), optional matching preceding_invoice.reference, and payment.iban (BT-84). Cannot be combined with full invoice_data."
          },
          "seller_context": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Seller Context",
            "description": "JSON seller inline context for BT-34/BT-22 (optional). electronic_address.value is max 125 chars; scheme_id is alphanumeric 2-20 chars. document_notes[] requires subject_code PMT, PMD or AAB and content max 500 chars; untyped notes are rejected."
          },
          "seller_profile_inline": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Seller Profile Inline",
            "description": "Alias for seller_context; no persistent profile is created or updated."
          },
          "buyer_context": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Buyer Context",
            "description": "JSON buyer inline context for BT-49 and alternate BT-22 document_notes (optional). electronic_address.value is max 125 chars; scheme_id is alphanumeric 2-20 chars. document_notes[] requires subject_code PMT, PMD or AAB and content max 500 chars; untyped notes are rejected."
          },
          "buyer_profile_inline": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Buyer Profile Inline",
            "description": "Alias for buyer_context; no persistent profile is created or updated."
          },
          "issuer_id": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Issuer Id",
            "description": "Tenant-scoped saved issuer selector for explicit profile reuse; requires profile_reuse=explicit."
          },
          "client_id": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Client Id",
            "description": "Tenant-scoped saved client selector for explicit profile reuse; requires profile_reuse=explicit."
          },
          "issuer_profile_id": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Issuer Profile Id",
            "description": "Alias for issuer_id; tenant-scoped and requires profile_reuse=explicit."
          },
          "client_profile_id": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Client Profile Id",
            "description": "Alias for client_id; tenant-scoped and requires profile_reuse=explicit."
          },
          "seller_profile_id": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Seller Profile Id",
            "description": "Tenant-scoped saved seller profile selector; requires profile_reuse=explicit."
          },
          "buyer_profile_id": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Buyer Profile Id",
            "description": "Tenant-scoped saved buyer profile selector; requires profile_reuse=explicit."
          },
          "profile_reuse": {
            "anyOf": [
              {
                "type": "string",
                "const": "explicit"
              },
              {
                "type": "null"
              }
            ],
            "title": "Profile Reuse",
            "description": "Saved-profile reuse policy. Use profile_reuse=explicit to allow tenant-scoped saved issuer/client fields to complete supported France CTC gaps."
          },
          "dry_run": {
            "type": "boolean",
            "title": "Dry Run",
            "description": "Run convert diagnostics without quota consumption, cloud OCR fallback, or PDF/A-3 packaging. Intended for scanner previews.",
            "default": false
          },
          "validation_target": {
            "anyOf": [
              {
                "type": "string",
                "pattern": "^(en16931|france_2026)$"
              },
              {
                "type": "null"
              }
            ],
            "title": "Validation Target",
            "description": "Explicit verification target. Use en16931 for Factur-X EN16931, or france_2026 for EN16931 plus available France 2026 / FNFE-MPE checks."
          }
        },
        "type": "object",
        "required": [
          "file"
        ],
        "title": "Body_convert_invoice_api_v1_convert_post"
      },
      "ConvertResponse-Output": {
        "properties": {
          "success": {
            "type": "boolean",
            "title": "Success",
            "description": "Whether the requested validation_target was verified. Processing or target-not-verified responses must use success=false.",
            "default": true
          },
          "result": {
            "$ref": "#/components/schemas/ConvertResultResponse-Output"
          },
          "target": {
            "$ref": "#/components/schemas/ValidationTargetContractPayload",
            "description": "Canonical requested/executed target truth. Runtime responses emit this object so clients never treat a conversion or validation as valid without knowing which target was verified."
          },
          "dry_run": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ],
            "title": "Dry Run",
            "description": "``true`` on dry-run responses (no quota consumed, no PDF produced). Absent on real convert responses."
          },
          "verdict": {
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "ready",
                  "auto_repairable",
                  "needs_confirmation",
                  "convert_with_inputs",
                  "erp_complete",
                  "unsupported"
                ]
              },
              {
                "type": "null"
              }
            ],
            "title": "Verdict",
            "description": "Dry-run verdict bucket. Closed domain (any other value is a contract violation): ``ready`` (validation passed), ``auto_repairable`` (engine fixes every remaining issue automatically — structural fixes or field-level repair from extraction evidence), ``needs_confirmation`` (every remaining issue is a candidate the user must confirm before ``/convert`` — typically a SIRET/SIREN found in an untrusted zone of the PDF), ``convert_with_inputs`` (the document can go through ``/convert`` after the supported business inputs listed in ``conversionInputRecipe.requiredInputs`` are provided), ``erp_complete`` (legacy alias for older clients; new responses should use ``convert_with_inputs``), ``unsupported`` (genuine blocker / exception case)."
          },
          "issues": {
            "anyOf": [
              {
                "items": {
                  "additionalProperties": true,
                  "type": "object"
                },
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "title": "Issues",
            "description": "Dry-run issue list (each item carries the same fields as ``result.validation.issues``)."
          },
          "extracted_data": {
            "anyOf": [
              {
                "additionalProperties": true,
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Extracted Data",
            "description": "Sanitised view of the data the engine would feed into ``/convert`` (no PDF/XML content)."
          },
          "missing_fields": {
            "anyOf": [
              {
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "title": "Missing Fields",
            "description": "Business fields the engine could not extract; the caller must provide them via ``invoice_data`` JSON to reach a conformant XML."
          },
          "recoverable_with_invoice_data": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ],
            "title": "Recoverable With Invoice Data",
            "description": "``true`` when at least one ``erp_must_complete`` issue is present, signalling the caller can repair via ``invoice_data``."
          },
          "requirements": {
            "anyOf": [
              {
                "items": {
                  "additionalProperties": true,
                  "type": "object"
                },
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "title": "Requirements",
            "description": "Typed Product Truth requirements. This replaces any generic 'ERP required' interpretation with explicit requirement types such as seller_profile_required, fr_ctc_blocker, or pa_status_pending."
          },
          "fieldProvenance": {
            "anyOf": [
              {
                "additionalProperties": {
                  "additionalProperties": true,
                  "type": "object"
                },
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Fieldprovenance",
            "description": "Per-critical-field provenance. Separates document/XML evidence, deterministic normalization, profile input, and missing values."
          },
          "conflicts": {
            "anyOf": [
              {
                "items": {
                  "additionalProperties": true,
                  "type": "object"
                },
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "title": "Conflicts",
            "description": "Typed conflicts between document evidence and profile or invoice override input. Conflicts are surfaced instead of silently overwriting visible document data."
          },
          "validationReadiness": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/ValidationReadinessResponse"
              },
              {
                "type": "null"
              }
            ],
            "description": "Five-layer Product Truth readiness contract (en16931, facturxProfile, frCtc, paAcceptance, packaging). The ``facturxProfile`` layer is the Factur-X 1.08 extension schematron — ``not_evaluated`` for EN16931-profile sources because the runtime only runs the CEN base schematron on that path. PA acceptance is hard-locked to ``not_submitted`` — never inferred from a green scan. See ``docs/VALIDATION_TOPOLOGY.md`` §7 for the full France-first claim policy."
          },
          "conversionInputRecipe": {
            "anyOf": [
              {
                "additionalProperties": true,
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Conversioninputrecipe",
            "description": "Product Truth conversion recipe explaining which missing or detected inputs can be supplied through the current /convert invoice_data contract, which inputs are API gaps, and which nonRunnableDecisions fail closed as explicit product decisions rather than available public actions."
          },
          "convertPreparation": {
            "anyOf": [
              {
                "additionalProperties": true,
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Convertpreparation",
            "description": "Scanner-facing `/convert` handoff grouped into extracted fields, suggested values, confirmations, user-provided values, editable templates, unsupported API gaps, and non-runnable product decisions. Clients should prefer this view for UI counts instead of reclassifying `conversionInputRecipe.requiredInputs`."
          },
          "nextAction": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/ConvertNextActionResponse"
              },
              {
                "type": "null"
              }
            ],
            "description": "Canonical dry-run next action. Present on dry-run responses so clients do not infer convertability from legacy verdict or summary counters. Absent on real conversion responses."
          },
          "diagnostics": {
            "anyOf": [
              {
                "items": {
                  "additionalProperties": true,
                  "type": "object"
                },
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "title": "Diagnostics",
            "description": "Free-form diagnostic entries surfaced on the dry-run error branch (``_format_convert_dry_run_error_response``). Each entry is a schema-less dict so the engine can carry arbitrary remediation hints; clients should treat unknown keys as forward-compatible."
          },
          "targetPreflight": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/TargetPreflightContractResponse"
              },
              {
                "type": "null"
              }
            ],
            "description": "Target-aware dry-run preflight contract. For ``validation_target=france_2026``, clients must use this object to decide whether the France 2026 checklist is complete, incomplete, unsupported, or failed; ``exhaustive=true`` is emitted only after candidate validation or an equivalent deterministic proof."
          }
        },
        "type": "object",
        "required": [
          "result",
          "target"
        ],
        "title": "ConvertResponse",
        "description": "Full response from /convert endpoint — success / dry-run\nbusiness path only.\n\nWraps the conversion result with top-level metadata. Covers:\n\n* **normal convert** — ``success`` + ``result``;\n* **dry-run business success / business error** (XML-upload path\n  and ``dry_run=true`` PDF where extraction succeeded) — adds\n  ``dry_run``, ``verdict``, ``issues``, ``extracted_data``,\n  ``missing_fields``, ``recoverable_with_invoice_data``.\n\nThe dry-run **extraction-failure** branch (where extraction\nfailed before validation could run) lives on a dedicated sibling\nenvelope :class:`ConvertDryRunErrorEnvelopeResponse` — published\nas a separate ``oneOf`` arm on the operation 200 schema. Its\n``result`` is a strict :class:`ConvertResultDryRunErrorResponse`\n(extraction null, locked-empty applied / artifacts, scope label\n``\"—\"``). That envelope cannot be expressed via this class —\n``result`` here is the success-mode shape only, with a mandatory\nextraction report.\n\nA ``model_validator`` rejects bodies that wear dry-run error\nmarkers (``success=False``, ``dry_run=True``, ``diagnostics``\nset) because those markers belong on the strict envelope: any\nsuch body must use ``ConvertDryRunErrorEnvelopeResponse`` and\ncarry the strict :class:`ConvertResultDryRunErrorResponse`\nresult. This closes the bypass Codex flagged on review 10 (a\nmutated body with success-shape ``result`` plus dry-run error\nmarkers would otherwise validate against this envelope).",
        "examples": [
          {
            "result": {
              "appliedValidationProfiles": [
                "facturx_en16931_base"
              ],
              "conversionSuccessful": true,
              "convertedAt": "2026-01-04T12:00:00+00:00",
              "durationMs": 2500,
              "extraction": {
                "pageCount": 1,
                "sourceType": "native_text"
              },
              "legalDisclaimer": "Validation technique documentaire. Ne vaut pas acceptation par une Plateforme Agréée ni conformité fiscale finale.",
              "messageType": "invoice",
              "packagingPerformed": true,
              "pdf": "JVBERi0xLjQK...",
              "pdfSize": 102400,
              "processedInvoicesCharged": 1,
              "remainingInvoices": 999,
              "skippedValidationProfiles": [
                {
                  "profile": "fr_ctc",
                  "reason": "feature_flag_disabled"
                }
              ],
              "targetProfile": "EN16931",
              "unsupportedValidationProfiles": [],
              "validation": {
                "profile": "EN16931",
                "valid": true
              },
              "validationArtifacts": [
                {
                  "family": "facturx",
                  "id": "facturx-1.08-en16931-cii",
                  "profile": "facturx_en16931_base",
                  "sha256": "0000000000000000000000000000000000000000000000000000000000000000",
                  "sourceAuthority": "FNFE-MPE",
                  "status": "available",
                  "syntax": "cii",
                  "version": "1.08"
                }
              ],
              "validationScopeLabel": "Factur-X EN16931 base",
              "validationStatus": "valid",
              "xml": "PD94bWwgdmVyc2lvbj0...",
              "xmlSize": 4096
            },
            "success": true,
            "target": {
              "requested": "en16931",
              "executed": "en16931",
              "status": "verified",
              "missing_inputs": [],
              "not_run_reason": null
            }
          }
        ]
      },
      "ConvertDryRunErrorEnvelopeResponse-Output": {
        "properties": {
          "success": {
            "type": "boolean",
            "const": false,
            "title": "Success",
            "description": "Always ``false`` on the dry-run error envelope. Mandatory: callers must emit the marker explicitly."
          },
          "dry_run": {
            "type": "boolean",
            "const": true,
            "title": "Dry Run",
            "description": "Always ``true`` on the dry-run error envelope. Mandatory: callers must emit the marker explicitly."
          },
          "verdict": {
            "type": "string",
            "enum": [
              "ready",
              "auto_repairable",
              "needs_confirmation",
              "convert_with_inputs",
              "erp_complete",
              "unsupported"
            ],
            "title": "Verdict",
            "description": "Dry-run verdict bucket. Closed domain: ``ready``, ``auto_repairable``, ``needs_confirmation``, ``convert_with_inputs``, ``erp_complete``, ``unsupported``. Mandatory on this variant because the route emits one for every dry-run error. See the success envelope's ``verdict`` description for per-value semantics."
          },
          "issues": {
            "items": {
              "additionalProperties": true,
              "type": "object"
            },
            "type": "array",
            "title": "Issues",
            "description": "Dry-run issue list. Mandatory on this variant — at least one issue is always synthesised when extraction fails so callers see the cause."
          },
          "extracted_data": {
            "type": "null",
            "title": "Extracted Data",
            "description": "Always ``null`` on the dry-run error envelope — no extraction was produced. Mandatory: callers must emit the explicit ``null`` so generated SDKs cannot mistake an absent key for a populated extracted-data block."
          },
          "missing_fields": {
            "items": {
              "type": "string"
            },
            "type": "array",
            "title": "Missing Fields",
            "description": "Business fields the engine could not extract. Mandatory on this variant; may be empty when the engine failed before field detection."
          },
          "recoverable_with_invoice_data": {
            "type": "boolean",
            "title": "Recoverable With Invoice Data",
            "description": "Whether at least one ``erp_must_complete`` issue is present, signalling the caller can repair via ``invoice_data``. Mandatory on this variant."
          },
          "requirements": {
            "items": {
              "additionalProperties": true,
              "type": "object"
            },
            "type": "array",
            "title": "Requirements",
            "description": "Typed Product Truth requirements for this dry-run error."
          },
          "fieldProvenance": {
            "additionalProperties": {
              "additionalProperties": true,
              "type": "object"
            },
            "type": "object",
            "title": "Fieldprovenance",
            "description": "Per-critical-field provenance, present even when extraction failed."
          },
          "conflicts": {
            "items": {
              "additionalProperties": true,
              "type": "object"
            },
            "type": "array",
            "title": "Conflicts",
            "description": "Profile/override conflicts. Empty when no external inputs were resolved."
          },
          "validationReadiness": {
            "$ref": "#/components/schemas/ValidationReadinessResponse",
            "description": "Five-layer Product Truth readiness contract (en16931, facturxProfile, frCtc, paAcceptance, packaging). Present on the dry-run error envelope so SDKs always see the same shape — see the success envelope above for the per-layer semantics."
          },
          "conversionInputRecipe": {
            "additionalProperties": true,
            "type": "object",
            "title": "Conversioninputrecipe",
            "description": "Product Truth conversion recipe, present even when extraction failed so clients do not infer a fake public conversion action. Includes nonRunnableDecisions when the correct public state is fail-closed rather than /convert."
          },
          "convertPreparation": {
            "additionalProperties": true,
            "type": "object",
            "title": "Convertpreparation",
            "description": "Scanner-facing `/convert` handoff grouped into extracted fields, suggestions, confirmations, user-provided values, editable templates, unsupported API gaps, and non-runnable product decisions."
          },
          "nextAction": {
            "$ref": "#/components/schemas/ConvertNextActionResponse",
            "description": "Canonical fail-closed dry-run action for this error envelope. Extraction-failure responses still publish it so generated SDKs do not have to special-case a missing action contract."
          },
          "diagnostics": {
            "items": {
              "additionalProperties": true,
              "type": "object"
            },
            "type": "array",
            "title": "Diagnostics",
            "description": "Free-form diagnostic entries surfaced on the dry-run error branch. Mandatory on this variant (the ``ConvertResponse`` parent makes it optional because the success envelope does not emit it)."
          },
          "result": {
            "$ref": "#/components/schemas/ConvertResultDryRunErrorResponse-Output",
            "description": "Strict result envelope: every locked invariant from :class:`ConvertResultDryRunErrorResponse` (extraction null, empty applied / artifacts, scope label ``\"—\"``, validation_status ``\"error\"``) applies."
          },
          "target": {
            "$ref": "#/components/schemas/ValidationTargetContractPayload",
            "description": "Canonical requested/executed target truth. Runtime responses emit this object so clients never treat a conversion or validation as valid without knowing which target was verified."
          },
          "targetPreflight": {
            "allOf": [
              {
                "$ref": "#/components/schemas/TargetPreflightContractResponse"
              }
            ],
            "description": "Target-aware dry-run preflight contract, mandatory on the dry-run error envelope so clients can render incomplete or unsupported target preflight states without guessing from legacy counters."
          }
        },
        "type": "object",
        "required": [
          "success",
          "dry_run",
          "verdict",
          "issues",
          "extracted_data",
          "missing_fields",
          "recoverable_with_invoice_data",
          "requirements",
          "fieldProvenance",
          "conflicts",
          "validationReadiness",
          "conversionInputRecipe",
          "convertPreparation",
          "nextAction",
          "diagnostics",
          "result",
          "target",
          "targetPreflight"
        ],
        "title": "ConvertDryRunErrorEnvelopeResponse",
        "description": "Strict envelope for the dry-run extraction-failure branch.\n\nSibling of :class:`ConvertResponse` for the dry-run error path.\nLocks every top-level marker the route emits so a generated SDK\nsees the error variant as a distinct ``oneOf`` arm at the top\nlevel — not just at the nested ``result`` level.\n\nRequired by Codex review 10: the parent envelope union on\n``ConvertResponse`` was bypassable because the loose ``result``\nunion accepted a success-shape result with a ``ConvertResponse``\nbody wearing dry-run error markers. ``ConvertResponse`` now\nrejects that bypass via a ``model_validator``; this dedicated\nenvelope additionally documents the strict shape in OpenAPI so\nclients can branch on the top-level variant via the ``oneOf``.\n\nLocked top-level markers\n------------------------\n\n* ``success`` is ``Literal[False]`` — the engine did not produce\n  a Factur-X output;\n* ``dry_run`` is ``Literal[True]`` — only emitted on dry-run;\n* ``extracted_data`` is locked to ``None`` — extraction failed;\n* ``diagnostics`` is mandatory (was optional on\n  :class:`ConvertResponse`) — clients always receive the\n  remediation hints carried by this branch;\n* ``result`` is ``ConvertResultDryRunErrorResponse`` (no Union)\n  — every locked-empty / locked-const invariant on the result\n  envelope is therefore re-locked at the parent level."
      },
      "Body_extract_facturx_api_v1_extract_post": {
        "properties": {
          "file": {
            "type": "string",
            "format": "binary",
            "title": "File",
            "description": "PDF file to extract XML from (Factur-X format)"
          }
        },
        "type": "object",
        "required": [
          "file"
        ],
        "title": "Body_extract_facturx_api_v1_extract_post"
      },
      "Body_repair_facturx_xml_api_v1_repair_post": {
        "properties": {
          "file": {
            "type": "string",
            "format": "binary",
            "title": "File",
            "description": "XML file to repair (Factur-X/EN16931 format)"
          },
          "constraints": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Constraints",
            "description": "JSON object with repair constraints (optional)"
          }
        },
        "type": "object",
        "required": [
          "file"
        ],
        "title": "Body_repair_facturx_xml_api_v1_repair_post"
      },
      "RepairResponse": {
        "properties": {
          "durationMs": {
            "type": "integer",
            "title": "Durationms",
            "description": "Time taken for repair in milliseconds"
          },
          "repaired_xml": {
            "type": "string",
            "title": "Repaired Xml",
            "description": "Base64-encoded repaired XML content"
          },
          "diff_summary": {
            "items": {
              "$ref": "#/components/schemas/DiffItemResponse"
            },
            "type": "array",
            "title": "Diff Summary",
            "description": "List of changes made during repair"
          },
          "target": {
            "$ref": "#/components/schemas/ValidationTargetContractPayload",
            "description": "Canonical requested/executed target truth for the repaired XML. repairSuccessful=true is only valid when this target is verified."
          },
          "repairSuccessful": {
            "type": "boolean",
            "title": "Repairsuccessful",
            "description": "Whether the repaired XML was verified for the requested target. Must be false when target.status is not verified."
          },
          "validation_before": {
            "$ref": "#/components/schemas/RepairValidationPayload",
            "description": "Validation result before repair"
          },
          "validation_after": {
            "$ref": "#/components/schemas/RepairValidationPayload",
            "description": "Validation result after repair"
          }
        },
        "type": "object",
        "required": [
          "durationMs",
          "repaired_xml",
          "diff_summary",
          "target",
          "repairSuccessful",
          "validation_before",
          "validation_after"
        ],
        "title": "RepairResponse",
        "description": "Response from /repair endpoint.\n\nDefines the API response schema for repair operations.\nMatches the RepairResponse schema in OPENAPI.yaml.\n\nAttributes:\n    duration_ms: Time taken for repair in milliseconds.\n    repaired_xml: Base64-encoded repaired XML content.\n    diff_summary: List of changes made during repair.\n    target: Canonical requested/executed validation target truth.\n    validation_before: Validation result before repair.\n    validation_after: Validation result after repair."
      },
      "Body_validate_facturx_api_v1_validate_post": {
        "properties": {
          "file": {
            "type": "string",
            "format": "binary",
            "title": "File",
            "description": "Fichier XML ou PDF Factur-X à valider (limite configurable via MAX_UPLOAD_SIZE_BYTES)",
            "examples": [
              "facture.xml",
              "invoice.pdf"
            ]
          }
        },
        "type": "object",
        "required": [
          "file"
        ],
        "title": "Body_validate_facturx_api_v1_validate_post"
      },
      "UnifiedValidationResponse-Output": {
        "properties": {
          "valid": {
            "type": "boolean",
            "title": "Valid"
          },
          "validatedAt": {
            "type": "string",
            "title": "Validatedat"
          },
          "filename": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Filename"
          },
          "profile": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Profile"
          },
          "durationMs": {
            "type": "integer",
            "minimum": 0,
            "title": "Durationms"
          },
          "summary": {
            "$ref": "#/components/schemas/ValidationSummaryResponse"
          },
          "issues": {
            "items": {
              "additionalProperties": true,
              "type": "object"
            },
            "type": "array",
            "title": "Issues"
          },
          "stages": {
            "additionalProperties": {
              "$ref": "#/components/schemas/ValidationStageResponse"
            },
            "type": "object",
            "title": "Stages"
          },
          "errors": {
            "items": {
              "additionalProperties": true,
              "type": "object"
            },
            "type": "array",
            "title": "Errors"
          },
          "warnings": {
            "items": {
              "additionalProperties": true,
              "type": "object"
            },
            "type": "array",
            "title": "Warnings"
          },
          "remediationReport": {
            "anyOf": [
              {
                "additionalProperties": true,
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Remediationreport"
          },
          "enrichment": {
            "anyOf": [
              {
                "additionalProperties": true,
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Enrichment"
          },
          "validationStatus": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Validationstatus"
          },
          "validationScopeLabel": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Validationscopelabel"
          },
          "appliedValidationProfiles": {
            "anyOf": [
              {
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "title": "Appliedvalidationprofiles"
          },
          "skippedValidationProfiles": {
            "anyOf": [
              {
                "items": {
                  "additionalProperties": true,
                  "type": "object"
                },
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "title": "Skippedvalidationprofiles"
          },
          "unsupportedValidationProfiles": {
            "anyOf": [
              {
                "items": {
                  "additionalProperties": true,
                  "type": "object"
                },
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "title": "Unsupportedvalidationprofiles"
          },
          "validationArtifacts": {
            "anyOf": [
              {
                "items": {
                  "additionalProperties": true,
                  "type": "object"
                },
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "title": "Validationartifacts"
          },
          "messageType": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Messagetype"
          },
          "legalDisclaimer": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Legaldisclaimer"
          },
          "validationTarget": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/ValidationTargetPolicyPayload-Output"
              },
              {
                "type": "null"
              }
            ]
          },
          "target": {
            "$ref": "#/components/schemas/ValidationTargetContractPayload",
            "description": "Canonical requested/executed target truth. Runtime responses emit this object so clients never treat a conversion or validation as valid without knowing which target was verified."
          },
          "validationReadiness": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/ValidationReadinessResponse"
              },
              {
                "type": "null"
              }
            ],
            "description": "Five-layer Product Truth readiness (en16931, facturxProfile, frCtc, paAcceptance, packaging). The ``facturxProfile`` layer is the Factur-X 1.08 extension schematron — ``not_evaluated`` for EN16931-profile sources because the runtime only runs the CEN base schematron on that path. See ``docs/VALIDATION_TOPOLOGY.md`` §7."
          }
        },
        "type": "object",
        "required": [
          "valid",
          "validatedAt",
          "durationMs",
          "summary",
          "target"
        ],
        "title": "UnifiedValidationResponse",
        "description": "Default /validate response format.\n\nThe legacy ValidationResponse shape is still available with ?legacy=true.",
        "examples": [
          {
            "durationMs": 124,
            "errors": [],
            "filename": "invoice.xml",
            "issues": [],
            "profile": "EN16931",
            "stages": {
              "facturx_pdf": {
                "error_count": 0,
                "info_count": 0,
                "issues": [],
                "passed": true,
                "skip_reason": "XML file - PDF checks not applicable",
                "skipped": true,
                "warning_count": 0
              },
              "pdfa": {
                "error_count": 0,
                "info_count": 0,
                "issues": [],
                "passed": true,
                "skip_reason": "XML file - PDF/A validation not applicable",
                "skipped": true,
                "warning_count": 0
              },
              "schematron": {
                "error_count": 0,
                "info_count": 0,
                "issues": [],
                "passed": true,
                "skipped": false,
                "warning_count": 0
              },
              "xsd": {
                "error_count": 0,
                "info_count": 0,
                "issues": [],
                "passed": true,
                "skipped": false,
                "warning_count": 0
              }
            },
            "summary": {
              "errorCount": 0,
              "warningCount": 0
            },
            "target": {
              "executed": "en16931",
              "missing_inputs": [],
              "requested": "en16931",
              "status": "verified",
              "not_run_reason": null
            },
            "valid": true,
            "validatedAt": "2026-04-28T10:00:00.000Z",
            "warnings": []
          }
        ]
      },
      "ValidationResponse": {
        "properties": {
          "valid": {
            "type": "boolean",
            "title": "Valid",
            "description": "True if the file is valid according to XSD and Schematron rules, False otherwise"
          },
          "errors": {
            "items": {
              "additionalProperties": {
                "type": "string"
              },
              "type": "object"
            },
            "type": "array",
            "title": "Errors",
            "description": "List of validation errors (XSD, Schematron, PDF processing)",
            "examples": [
              [
                {
                  "line": "42",
                  "message": "Missing required element: InvoiceNumber",
                  "type": "xsd_validation_error"
                }
              ]
            ]
          },
          "warnings": {
            "items": {
              "additionalProperties": {
                "type": "string"
              },
              "type": "object"
            },
            "type": "array",
            "title": "Warnings",
            "description": "List of validation warnings (non-blocking issues)",
            "examples": [
              [
                {
                  "message": "Recommended field missing",
                  "type": "schematron_report"
                }
              ]
            ]
          },
          "profile": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Profile",
            "description": "Detected Factur-X profile (MINIMUM, BASIC_WL, BASIC, EN16931, EXTENDED, or UNKNOWN)",
            "examples": [
              "EN16931",
              "BASIC",
              "MINIMUM",
              "EXTENDED"
            ]
          },
          "message": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Message",
            "description": "Human-readable summary message",
            "examples": [
              "Validation completed",
              "Validation failed"
            ]
          },
          "target": {
            "$ref": "#/components/schemas/ValidationTargetContractPayload",
            "description": "Canonical requested/executed target truth. Runtime responses emit this object so clients never treat a conversion or validation as valid without knowing which target was verified."
          }
        },
        "type": "object",
        "required": [
          "valid",
          "target"
        ],
        "title": "ValidationResponse",
        "description": "Response from validation endpoint.\n\nContains the validation result with detailed errors and warnings.",
        "examples": [
          {
            "errors": [],
            "message": "Validation completed",
            "profile": "EN16931",
            "target": {
              "requested": "en16931",
              "executed": "en16931",
              "status": "verified",
              "missing_inputs": [],
              "not_run_reason": null
            },
            "valid": true,
            "warnings": []
          },
          {
            "errors": [
              {
                "line": "5",
                "message": "Element 'invoice': Missing child element(s). Expected is ( number ).",
                "type": "xsd_validation_error"
              }
            ],
            "message": "Validation failed",
            "target": {
              "missing_inputs": [],
              "requested": "en16931",
              "status": "failed",
              "executed": null,
              "not_run_reason": null
            },
            "valid": false,
            "warnings": []
          }
        ]
      },
      "MetaResponse": {
        "properties": {
          "build": {
            "$ref": "#/components/schemas/BuildInfo"
          },
          "validation_engine": {
            "$ref": "#/components/schemas/ValidationEngineInfo"
          },
          "limits": {
            "$ref": "#/components/schemas/LimitsInfo"
          },
          "plans": {
            "anyOf": [
              {
                "items": {
                  "$ref": "#/components/schemas/app__infrastructure__api__routes__meta__PlanInfo"
                },
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "title": "Plans"
          },
          "capabilities": {
            "$ref": "#/components/schemas/CapabilitiesInfo"
          },
          "links": {
            "$ref": "#/components/schemas/LinksInfo"
          }
        },
        "type": "object",
        "required": [
          "build",
          "validation_engine",
          "limits",
          "capabilities",
          "links"
        ],
        "title": "MetaResponse"
      },
      "ConvertResultResponse-Output": {
        "properties": {
          "durationMs": {
            "type": "integer",
            "title": "Durationms"
          },
          "convertedAt": {
            "type": "string",
            "title": "Convertedat"
          },
          "targetProfile": {
            "type": "string",
            "title": "Targetprofile"
          },
          "conversionSuccessful": {
            "type": "boolean",
            "title": "Conversionsuccessful"
          },
          "xmlSize": {
            "type": "integer",
            "title": "Xmlsize"
          },
          "xml": {
            "type": "string",
            "title": "Xml",
            "description": "Base64-encoded Factur-X XML when target.status=verified"
          },
          "status": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Status",
            "description": "vNext status: gold, silver, fail, processing"
          },
          "jobId": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Jobid",
            "description": "Async job identifier when applicable"
          },
          "score": {
            "anyOf": [
              {
                "additionalProperties": true,
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Score"
          },
          "pipeline": {
            "anyOf": [
              {
                "additionalProperties": true,
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Pipeline"
          },
          "fallback": {
            "anyOf": [
              {
                "additionalProperties": true,
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Fallback"
          },
          "timings": {
            "anyOf": [
              {
                "additionalProperties": {
                  "type": "integer"
                },
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Timings"
          },
          "cost": {
            "anyOf": [
              {
                "additionalProperties": {
                  "anyOf": [
                    {
                      "type": "integer"
                    },
                    {
                      "type": "number"
                    },
                    {
                      "type": "string"
                    }
                  ]
                },
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Cost"
          },
          "proofPack": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/ProofPackResponse"
              },
              {
                "type": "null"
              }
            ]
          },
          "pdf": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Pdf",
            "description": "Base64-encoded PDF when target.status=verified and ephemeral mode is used"
          },
          "pdfSize": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Pdfsize",
            "description": "PDF size in bytes when target.status=verified and ephemeral mode is used"
          },
          "pdfUrl": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Pdfurl",
            "description": "Presigned download URL when target.status=verified and persistent mode is used"
          },
          "extraction": {
            "$ref": "#/components/schemas/ExtractionReportResponse"
          },
          "validation": {
            "additionalProperties": true,
            "type": "object",
            "title": "Validation"
          },
          "packagingPerformed": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ],
            "title": "Packagingperformed",
            "description": "Whether the conversion produced a PDF/A-3 artifact (``False`` for ``mode=xml_only`` and dry-run paths)."
          },
          "processedInvoicesCharged": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Processedinvoicescharged",
            "description": "Quota units consumed by this call (0 on dry-run paths)."
          },
          "remainingInvoices": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Remaininginvoices",
            "description": "Remaining quota for the caller's billing period."
          },
          "ops": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Ops",
            "description": "Internal operations counter mirrored on dry-run responses (always 0)."
          },
          "enrichment": {
            "anyOf": [
              {
                "additionalProperties": true,
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Enrichment",
            "description": "SIRENE / company enrichment metadata when the engine augmented the extracted invoice data."
          },
          "validationStatus": {
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "valid",
                  "invalid",
                  "error"
                ]
              },
              {
                "type": "null"
              }
            ],
            "title": "Validationstatus",
            "description": "Coarse-grained outcome of the EN16931 base run + every ``applied`` profile combined. Closed enum: ``valid`` | ``invalid`` | ``error``."
          },
          "validationScopeLabel": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Validationscopelabel",
            "description": "Human-readable summary of the profiles actually applied (e.g. ``Factur-X EN16931 base + France CTC FNFE-MPE v1.3.1``)."
          },
          "appliedValidationProfiles": {
            "items": {
              "type": "string",
              "enum": [
                "facturx_en16931_base",
                "facturx_extended",
                "fr_ctc",
                "chorus_b2g"
              ]
            },
            "type": "array",
            "title": "Appliedvalidationprofiles",
            "description": "List of profile identifiers that were actually executed."
          },
          "skippedValidationProfiles": {
            "items": {
              "$ref": "#/components/schemas/SkippedValidationProfileResponse"
            },
            "type": "array",
            "title": "Skippedvalidationprofiles",
            "description": "Profiles the engine considered but did not execute (AUTO derivation could not run today). Each entry pins ``profile`` + ``reason`` so callers can render an honest summary."
          },
          "unsupportedValidationProfiles": {
            "items": {
              "$ref": "#/components/schemas/UnsupportedValidationProfileResponse"
            },
            "type": "array",
            "title": "Unsupportedvalidationprofiles",
            "description": "Reserved for future contract surface (deprecated profiles, plan-gated profiles). PR 2's resolver leaves this empty: an explicit profile that cannot run raises HTTP 422 ``validation_profile_unavailable`` instead."
          },
          "validationArtifacts": {
            "items": {
              "$ref": "#/components/schemas/ValidationArtifactResponse"
            },
            "type": "array",
            "title": "Validationartifacts",
            "description": "Provenance of every Schematron / XSLT pack that participated in validation. Stable across requests for the same engine version (sha256-pinned)."
          },
          "messageType": {
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "invoice",
                  "lifecycle_status_cdar"
                ]
              },
              {
                "type": "null"
              }
            ],
            "title": "Messagetype",
            "description": "``invoice`` | ``lifecycle_status_cdar``."
          },
          "legalDisclaimer": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Legaldisclaimer",
            "description": "Canonical phrase asserting the technical scope of the validation. The wedge does NOT promise PA acceptance or fiscal conformance — clients must surface this disclaimer alongside any displayed validation outcome."
          },
          "validationTarget": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/ValidationTargetPolicyPayload-Output"
              },
              {
                "type": "null"
              }
            ],
            "description": "Explains why France CTC was targeted, skipped, or considered conflicted: explicit profile, request jurisdiction, document seller/buyer country evidence, runtime unavailability, and PA lifecycle separation."
          }
        },
        "type": "object",
        "required": [
          "durationMs",
          "convertedAt",
          "targetProfile",
          "conversionSuccessful",
          "xmlSize",
          "xml",
          "extraction",
          "validation"
        ],
        "title": "ConvertResultResponse",
        "description": "Convert result in API response format.\n\nGenerated deliverables are target-gated by the top-level ``target``\ncontract: ``pdf``, ``pdfUrl``, ``xml``, proof-pack links and\n``conversionSuccessful=true`` are only available when\n``target.status=verified`` for the requested ``validation_target``.\n\nImplements oneOf constraint when deliverables are exposed: either\n'pdf' or 'pdfUrl' is present, never both.\n\nAttributes:\n    duration_ms: Time taken for conversion in milliseconds.\n    converted_at: ISO timestamp of conversion.\n    target_profile: Target Factur-X profile used.\n    conversion_successful: Whether the requested target was verified.\n    xml_size: Size of embedded XML in bytes.\n    xml: Base64-encoded Factur-X XML when target.status=verified.\n    pdf: Base64-encoded PDF when target.status=verified and ephemeral mode is used.\n    pdf_size: Size of PDF in bytes when target.status=verified and ephemeral mode is used.\n    pdf_url: Presigned download URL when target.status=verified and persistent mode is used.\n    extraction: Extraction report metadata.\n    validation: Validation result details.\n\nGenerated deliverables are target-gated: callers must request `validation_target`, and PDF/XML artifacts are exposed only when `target.status=verified` for that requested target."
      },
      "ValidationTargetContractPayload": {
        "properties": {
          "requested": {
            "type": "string",
            "enum": [
              "en16931",
              "france_2026"
            ],
            "title": "Requested",
            "description": "Target requested by the caller or by the explicit response default."
          },
          "executed": {
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "en16931",
                  "france_2026"
                ]
              },
              {
                "type": "null"
              }
            ],
            "title": "Executed",
            "description": "Requested target that actually ran. Null means the target was not verified."
          },
          "status": {
            "type": "string",
            "enum": [
              "verified",
              "failed",
              "blocked",
              "not_run"
            ],
            "title": "Status",
            "description": "verified, failed, blocked by inputs, or not_run."
          },
          "missing_inputs": {
            "items": {
              "type": "string"
            },
            "type": "array",
            "title": "Missing Inputs",
            "description": "Inputs that still block verification of the requested target."
          },
          "not_run_reason": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Not Run Reason",
            "description": "Stable reason when the target could not be executed."
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "requested",
          "executed",
          "status",
          "missing_inputs",
          "not_run_reason"
        ],
        "title": "ValidationTargetContractPayload",
        "description": "Short target truth contract emitted by /convert and /validate."
      },
      "ValidationReadinessResponse": {
        "properties": {
          "en16931": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ValidationReadinessSectionResponse"
              }
            ],
            "description": "Pure CEN EN16931 base schematron outcome. ``passed`` means the runtime CEN compiled XSLT found no error; it does NOT mean the Factur-X 1.08 extension layer was checked — see ``facturxProfile`` for that distinction."
          },
          "facturxProfile": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ValidationReadinessSectionResponse"
              }
            ],
            "description": "Factur-X 1.08 profile schematron outcome (the extension layer on top of CEN EN16931). Today's runtime only runs this layer for non-EN16931 source profiles (BASIC, BASIC_WL, EXTENDED, MINIMUM). For EN16931-profile sources the status is ``not_evaluated`` and ``evidence`` is ``factur_x_1_08_extensions_not_runtime_observed`` — see ``docs/VALIDATION_TOPOLOGY.md``."
          },
          "frCtc": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ValidationReadinessSectionResponse"
              }
            ],
            "description": "France CTC / FNFE-MPE FR CTC v1.3.1 outcome. Only run for FR jurisdiction (auto-detected or explicit ``jurisdiction=FR``). Out-of-FR fixtures see ``status='not_evaluated'`` with ``evidence='out_of_france_ctc_scope'`` or similar."
          },
          "paAcceptance": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ValidationReadinessSectionResponse"
              }
            ],
            "description": "Plateforme Agréée acceptance lifecycle. Hard-locked to ``status='not_submitted'`` by the engine — PA acceptance is NEVER inferred from a green validation stack. The invariant validator (INV-025) refuses any drift."
          },
          "packaging": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ValidationReadinessSectionResponse"
              }
            ],
            "description": "PDF/A-3 packaging / Factur-X attachment registration outcome. ``passed`` when veraPDF + the Factur-X PDF checks service confirm the envelope; ``blocked`` when structural issues (XMP, /AF embedding) prevent a clean packaging; ``not_evaluated`` for xml_only dry-run mode."
          }
        },
        "additionalProperties": true,
        "type": "object",
        "required": [
          "en16931",
          "facturxProfile",
          "frCtc",
          "paAcceptance",
          "packaging"
        ],
        "title": "ValidationReadinessResponse",
        "description": "Five-layer Product Truth readiness contract.\n\nThe five layers are ALWAYS emitted (``facturxProfile`` was added in\n``task_9.md`` Phase 2 and is part of this contract). A caller can\nread each layer independently to tell exactly which schematron /\nvalidator ran. No higher-level field in the payload is allowed to\ncompress these five layers into a single positive answer — the\ninvariant validator in\n``app/domain/services/product_truth_invariants.py`` (INV-024 /\nINV-025 / INV-026) refuses to ship such a payload.",
        "examples": [
          {
            "en16931": {
              "blockerCount": 0,
              "blockers": [],
              "evidence": "en16931_base_passed",
              "status": "passed"
            },
            "facturxProfile": {
              "blockerCount": 0,
              "blockers": [],
              "evidence": "factur_x_1_08_extensions_not_runtime_observed",
              "reason": "factur_x_1_08_profile_schematron_not_runtime_observable_for_en16931",
              "status": "not_evaluated",
              "target": "factur_x_1_08_extensions"
            },
            "frCtc": {
              "blockerCount": 0,
              "blockers": [],
              "evidence": "fr_ctc_passed",
              "status": "passed"
            },
            "paAcceptance": {
              "blockerCount": 0,
              "blockers": [],
              "evidence": "pa_lifecycle_not_part_of_validation_target",
              "reason": "pa_lifecycle_not_part_of_validation_target",
              "status": "not_submitted",
              "target": "not_submitted"
            },
            "packaging": {
              "blockerCount": 0,
              "blockers": [],
              "evidence": "packaging_passed",
              "status": "passed"
            }
          },
          {
            "en16931": {
              "blockerCount": 0,
              "blockers": [],
              "status": "passed"
            },
            "facturxProfile": {
              "blockerCount": 0,
              "blockers": [],
              "evidence": "factur_x_1_08_extensions_not_runtime_observed",
              "status": "not_evaluated"
            },
            "frCtc": {
              "blockerCount": 1,
              "blockers": [
                "BR-FR-12_BT-49"
              ],
              "evidence": "fr_ctc_failed_assert",
              "status": "blocked"
            },
            "paAcceptance": {
              "blockerCount": 0,
              "blockers": [],
              "status": "not_submitted"
            },
            "packaging": {
              "blockerCount": 0,
              "blockers": [],
              "evidence": "packaging_skipped_xml_only",
              "status": "not_evaluated"
            }
          }
        ]
      },
      "TargetPreflightContractResponse": {
        "properties": {
          "requested": {
            "type": "string",
            "enum": [
              "en16931",
              "france_2026"
            ],
            "title": "Requested",
            "description": "Requested validation target for this preflight contract."
          },
          "status": {
            "type": "string",
            "enum": [
              "complete",
              "incomplete",
              "unsupported",
              "failed"
            ],
            "title": "Status",
            "description": "Whether the target-aware preflight is complete, incomplete, blocked by unsupported items or failed during proof execution."
          },
          "exhaustive": {
            "type": "boolean",
            "title": "Exhaustive",
            "description": "true only when candidate validation, or an equivalent deterministic proof, has made the requiredInputs and unsupportedBlockers lists exhaustive."
          },
          "basis": {
            "type": "string",
            "enum": [
              "candidate_validation",
              "known_rule_map",
              "source_validation"
            ],
            "title": "Basis",
            "description": "Evidence basis used to build the preflight contract."
          },
          "requiredInputs": {
            "items": {
              "$ref": "#/components/schemas/ProductTruthConversionInputResponse"
            },
            "type": "array",
            "title": "Requiredinputs",
            "description": "Supported caller inputs that must be replayed from conversionInputRecipe.examplePayload."
          },
          "automaticFixes": {
            "items": {
              "$ref": "#/components/schemas/ProductTruthConversionInputResponse"
            },
            "type": "array",
            "title": "Automaticfixes",
            "description": "Target blockers covered by automatic /convert fixes after the recipe payload is applied."
          },
          "unsupportedBlockers": {
            "items": {
              "$ref": "#/components/schemas/ProductTruthConversionInputResponse"
            },
            "type": "array",
            "title": "Unsupportedblockers",
            "description": "Target blockers not covered by current public /convert inputs or automatic fixes."
          },
          "routedInputs": {
            "items": {
              "$ref": "#/components/schemas/ProductTruthConversionInputResponse"
            },
            "type": "array",
            "title": "Routedinputs",
            "description": "Supported target blocker inputs with their public /convert route paths."
          },
          "remainingAfterInputsPolicy": {
            "type": "string",
            "enum": [
              "none_expected",
              "unknown",
              "possible"
            ],
            "title": "Remainingafterinputspolicy",
            "description": "Policy for blockers after the supported inputs are supplied. unknown/possible means clients must not present guided conversion as complete."
          }
        },
        "additionalProperties": true,
        "type": "object",
        "required": [
          "requested",
          "status",
          "exhaustive",
          "basis",
          "requiredInputs",
          "automaticFixes",
          "unsupportedBlockers",
          "routedInputs",
          "remainingAfterInputsPolicy"
        ],
        "title": "TargetPreflightContractResponse",
        "description": "France 2026 target-aware dry-run preflight contract."
      },
      "ConvertResultDryRunErrorResponse-Output": {
        "properties": {
          "durationMs": {
            "type": "integer",
            "title": "Durationms"
          },
          "convertedAt": {
            "type": "string",
            "title": "Convertedat"
          },
          "targetProfile": {
            "type": "string",
            "title": "Targetprofile"
          },
          "conversionSuccessful": {
            "type": "boolean",
            "const": false,
            "title": "Conversionsuccessful",
            "description": "Always ``False`` on this branch — the engine did not produce a Factur-X output."
          },
          "xmlSize": {
            "type": "integer",
            "const": 0,
            "title": "Xmlsize",
            "description": "Always ``0`` — no XML was emitted."
          },
          "xml": {
            "type": "string",
            "const": "",
            "title": "Xml",
            "description": "Always the empty string — no XML was emitted. Mandatory: callers must emit the explicit empty string."
          },
          "extraction": {
            "type": "null",
            "title": "Extraction",
            "description": "Always ``null`` on the dry-run error branch (no extraction report was produced). Mandatory: the explicit ``null`` must be emitted so generated SDKs cannot mistake an absent key for a populated extraction."
          },
          "validation": {
            "additionalProperties": true,
            "type": "object",
            "title": "Validation",
            "description": "Mirrors :class:`ConvertResultResponse.validation` — the stages metadata still ships so callers can render the diagnostic without branching on the result variant."
          },
          "packagingPerformed": {
            "type": "boolean",
            "const": false,
            "title": "Packagingperformed",
            "description": "Always ``False`` — no PDF/A-3 was packaged. Mandatory: callers must emit the explicit ``false``."
          },
          "processedInvoicesCharged": {
            "type": "integer",
            "const": 0,
            "title": "Processedinvoicescharged",
            "description": "Always ``0`` — dry-run never consumes quota. Mandatory: callers must emit the explicit ``0``."
          },
          "ops": {
            "type": "integer",
            "const": 0,
            "title": "Ops",
            "description": "Always ``0`` — no internal operation was emitted. Mandatory: callers must emit the explicit ``0``."
          },
          "validationStatus": {
            "type": "string",
            "const": "error",
            "title": "Validationstatus",
            "description": "Always ``\"error\"`` on the dry-run extraction-failure branch. The success-path variant accepts the broader enum (``valid`` | ``invalid`` | ``error``)."
          },
          "validationScopeLabel": {
            "type": "string",
            "const": "—",
            "title": "Validationscopelabel",
            "description": "Always ``\"—\"`` (U+2014 EM DASH) on this variant — nothing applied, so no scope label is meaningful. Locked to that sentinel so a future emitter cannot advertise a human-facing scope that contradicts the empty applied / artifacts contract."
          },
          "appliedValidationProfiles": {
            "items": {
              "type": "string",
              "enum": [
                "facturx_en16931_base",
                "facturx_extended",
                "fr_ctc",
                "chorus_b2g"
              ]
            },
            "type": "array",
            "maxItems": 0,
            "title": "Appliedvalidationprofiles",
            "description": "Always the empty array on this branch — no profile actually executed because extraction failed first. Locked empty (``maxItems: 0``) so the contract cannot claim a run that never happened."
          },
          "skippedValidationProfiles": {
            "items": {
              "$ref": "#/components/schemas/SkippedValidationProfileResponse"
            },
            "type": "array",
            "title": "Skippedvalidationprofiles",
            "description": "Profiles the engine considered but did not execute. Preserved across the success / error variants so AUTO + jurisdiction=FR still surfaces ``fr_ctc`` with its non-execution reason. Mandatory on this variant."
          },
          "unsupportedValidationProfiles": {
            "items": {
              "$ref": "#/components/schemas/UnsupportedValidationProfileResponse"
            },
            "type": "array",
            "title": "Unsupportedvalidationprofiles",
            "description": "Reserved for future contract surface; PR 2's resolver leaves this empty (explicit non-runnable profiles raise HTTP 422 ``validation_profile_unavailable``). Mandatory: the empty array must be emitted explicitly."
          },
          "validationArtifacts": {
            "items": {
              "$ref": "#/components/schemas/ValidationArtifactResponse"
            },
            "type": "array",
            "maxItems": 0,
            "title": "Validationartifacts",
            "description": "Always the empty array on the dry-run error branch — no artifact actually participated in this request. Locked empty (``maxItems: 0``) so a generated client cannot validate an artifact entry on a branch where validation never ran."
          },
          "messageType": {
            "type": "string",
            "enum": [
              "invoice",
              "lifecycle_status_cdar"
            ],
            "title": "Messagetype",
            "description": "``invoice`` | ``lifecycle_status_cdar``."
          },
          "legalDisclaimer": {
            "type": "string",
            "title": "Legaldisclaimer",
            "description": "Canonical phrase asserting the technical scope of the validation. Surfaced even on the error branch so callers consistently render the wedge's legal posture."
          },
          "validationTarget": {
            "$ref": "#/components/schemas/ValidationTargetPolicyPayload-Output",
            "description": "Target policy explanation for the dry-run error branch. No validation ran, but the response still states whether France CTC was requested, derived, skipped, or conflicted."
          }
        },
        "type": "object",
        "required": [
          "durationMs",
          "convertedAt",
          "targetProfile",
          "conversionSuccessful",
          "xmlSize",
          "xml",
          "extraction",
          "validation",
          "packagingPerformed",
          "processedInvoicesCharged",
          "ops",
          "validationStatus",
          "validationScopeLabel",
          "appliedValidationProfiles",
          "skippedValidationProfiles",
          "unsupportedValidationProfiles",
          "validationArtifacts",
          "messageType",
          "legalDisclaimer",
          "validationTarget"
        ],
        "title": "ConvertResultDryRunErrorResponse",
        "description": "Result envelope for the dry-run extraction-failure branch.\n\nEmitted by ``_format_convert_dry_run_error_response`` when the\nengine could not extract enough data to even attempt validation.\nDistinct from :class:`ConvertResultResponse` so the public schema\ndocuments the reduced shape:\n\n* ``extraction`` is always ``None`` (no extraction was produced);\n* ``conversion_successful`` is locked to ``False``;\n* ``xml_size`` is locked to ``0`` and ``xml`` to the empty string;\n* ``packaging_performed`` / ``processed_invoices_charged`` /\n  ``ops`` are locked to their no-op values;\n* the PR 2 validation profile contract block is **mandatory** —\n  every dry-run error response carries it (``validation_status =\n  \"error\"`` with empty ``applied`` and accurate ``skipped``).\n\nGenerated SDKs reach this shape through\n:class:`ConvertDryRunErrorEnvelopeResponse` — the strict sibling\nenvelope published as the second top-level ``oneOf`` arm on the\noperation 200 response. ``ConvertResponse.result`` is\nintentionally narrowed to :class:`ConvertResultResponse` so the\nsuccess / dry-run-business arm cannot represent the\nextraction-failure shape, and the operation 200 union arms stay\nnon-overlapping (``ConvertResponse |\nConvertDryRunErrorEnvelopeResponse``)."
      },
      "DiffItemResponse": {
        "type": "object",
        "title": "DiffItemResponse",
        "required": [
          "code",
          "path",
          "message"
        ],
        "properties": {
          "code": {
            "type": "string",
            "description": "Type of repair action performed"
          },
          "path": {
            "type": "string",
            "description": "XPath to the modified element"
          },
          "message": {
            "type": "string",
            "description": "Description of the change made"
          },
          "beforeValue": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Original value before modification"
          },
          "afterValue": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "New value after modification"
          },
          "ruleReference": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Validation rule that triggered this repair"
          }
        }
      },
      "RepairValidationPayload": {
        "properties": {
          "valid": {
            "type": "boolean",
            "title": "Valid",
            "description": "Whether this validation pass is valid"
          },
          "target": {
            "$ref": "#/components/schemas/ValidationTargetContractPayload",
            "description": "Target truth for this validation pass"
          },
          "summary": {
            "additionalProperties": true,
            "type": "object",
            "title": "Summary",
            "description": "Validation issue counters"
          }
        },
        "additionalProperties": true,
        "type": "object",
        "required": [
          "valid",
          "target"
        ],
        "title": "RepairValidationPayload",
        "description": "Validation payload embedded in /repair before/after results."
      },
      "ValidationSummaryResponse": {
        "properties": {
          "errorCount": {
            "type": "integer",
            "minimum": 0,
            "title": "Errorcount"
          },
          "warningCount": {
            "type": "integer",
            "minimum": 0,
            "title": "Warningcount"
          }
        },
        "type": "object",
        "required": [
          "errorCount",
          "warningCount"
        ],
        "title": "ValidationSummaryResponse",
        "description": "Aggregated validation counters."
      },
      "ValidationStageResponse": {
        "properties": {
          "passed": {
            "type": "boolean",
            "title": "Passed"
          },
          "skipped": {
            "type": "boolean",
            "title": "Skipped",
            "default": false
          },
          "skip_reason": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Skip Reason"
          },
          "error_count": {
            "type": "integer",
            "minimum": 0,
            "title": "Error Count",
            "default": 0
          },
          "warning_count": {
            "type": "integer",
            "minimum": 0,
            "title": "Warning Count",
            "default": 0
          },
          "info_count": {
            "type": "integer",
            "minimum": 0,
            "title": "Info Count",
            "default": 0
          },
          "issues": {
            "items": {
              "additionalProperties": true,
              "type": "object"
            },
            "type": "array",
            "title": "Issues"
          }
        },
        "type": "object",
        "required": [
          "passed"
        ],
        "title": "ValidationStageResponse",
        "description": "Result for one validation pipeline stage."
      },
      "BuildInfo": {
        "properties": {
          "commit_sha": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Commit Sha",
            "description": "Git commit SHA for this build, if provided at build time"
          },
          "build_date": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Build Date",
            "description": "Build date/time, if provided at build time"
          },
          "env": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Env",
            "description": "Runtime environment identifier (development/production/test)"
          }
        },
        "type": "object",
        "title": "BuildInfo"
      },
      "ValidationEngineInfo": {
        "properties": {
          "artifacts": {
            "items": {
              "$ref": "#/components/schemas/ValidationArtifact"
            },
            "type": "array",
            "title": "Artifacts"
          },
          "pdfa": {
            "$ref": "#/components/schemas/PdfaEngineInfo"
          }
        },
        "type": "object",
        "required": [
          "artifacts",
          "pdfa"
        ],
        "title": "ValidationEngineInfo"
      },
      "LimitsInfo": {
        "properties": {
          "max_upload_size_bytes": {
            "type": "integer",
            "title": "Max Upload Size Bytes"
          },
          "accepted_formats": {
            "items": {
              "type": "string"
            },
            "type": "array",
            "title": "Accepted Formats"
          },
          "rate_limit_enabled": {
            "type": "boolean",
            "title": "Rate Limit Enabled"
          },
          "rate_limit_requests_per_minute": {
            "type": "integer",
            "title": "Rate Limit Requests Per Minute"
          },
          "rate_limit_mode": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Rate Limit Mode"
          }
        },
        "type": "object",
        "required": [
          "max_upload_size_bytes",
          "accepted_formats",
          "rate_limit_enabled",
          "rate_limit_requests_per_minute"
        ],
        "title": "LimitsInfo"
      },
      "app__infrastructure__api__routes__meta__PlanInfo": {
        "properties": {
          "code": {
            "type": "string",
            "title": "Code"
          },
          "name": {
            "type": "string",
            "title": "Name"
          },
          "monthly_quota": {
            "type": "integer",
            "title": "Monthly Quota"
          },
          "price": {
            "anyOf": [
              {},
              {
                "type": "null"
              }
            ],
            "title": "Price"
          }
        },
        "type": "object",
        "required": [
          "code",
          "name",
          "monthly_quota"
        ],
        "title": "PlanInfo"
      },
      "CapabilitiesInfo": {
        "properties": {
          "supports_facturx_108_xsd": {
            "type": "boolean",
            "title": "Supports Facturx 108 Xsd"
          },
          "supports_en16931_compiled_xslt": {
            "type": "boolean",
            "title": "Supports En16931 Compiled Xslt"
          },
          "supports_pdfa_via_verapdf": {
            "type": "boolean",
            "title": "Supports Pdfa Via Verapdf"
          },
          "facturx_108_schematron_guaranteed": {
            "type": "boolean",
            "title": "Facturx 108 Schematron Guaranteed",
            "description": "True when Factur-X 1.08 Schematron validation for all profiles is guaranteed (Saxon available and compiled artefacts bundled)."
          },
          "supports_extract": {
            "type": "boolean",
            "title": "Supports Extract",
            "description": "True when POST /api/v1/extract endpoint is available. Always true as extraction has no external dependencies.",
            "default": true
          },
          "supports_repair": {
            "type": "boolean",
            "title": "Supports Repair",
            "description": "True when POST /api/v1/repair endpoint is available. Requires Saxon processor for Schematron validation.",
            "default": false
          },
          "supports_convert": {
            "type": "boolean",
            "title": "Supports Convert",
            "description": "True when POST /api/v1/convert endpoint is available. Requires callas pdfToolbox (REST or CLI).",
            "default": false
          }
        },
        "type": "object",
        "required": [
          "supports_facturx_108_xsd",
          "supports_en16931_compiled_xslt",
          "supports_pdfa_via_verapdf",
          "facturx_108_schematron_guaranteed"
        ],
        "title": "CapabilitiesInfo"
      },
      "LinksInfo": {
        "properties": {
          "docs": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Docs"
          },
          "openapi": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Openapi"
          },
          "changelog": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Changelog"
          },
          "status": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Status"
          },
          "generated_at": {
            "type": "string",
            "title": "Generated At"
          }
        },
        "type": "object",
        "required": [
          "generated_at"
        ],
        "title": "LinksInfo"
      },
      "ProofPackResponse": {
        "properties": {
          "url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Url"
          },
          "expiresAt": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Expiresat"
          }
        },
        "type": "object",
        "title": "ProofPackResponse",
        "description": "Proof-pack signed URL metadata for conversion responses."
      },
      "ExtractionReportResponse": {
        "properties": {
          "sourceType": {
            "type": "string",
            "title": "Sourcetype"
          },
          "pageCount": {
            "type": "integer",
            "title": "Pagecount"
          },
          "pageConfidences": {
            "anyOf": [
              {
                "items": {
                  "additionalProperties": {
                    "anyOf": [
                      {
                        "type": "integer"
                      },
                      {
                        "type": "number"
                      }
                    ]
                  },
                  "type": "object"
                },
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "title": "Pageconfidences"
          },
          "overallConfidence": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "title": "Overallconfidence"
          },
          "ocrProcessingTimeMs": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Ocrprocessingtimems"
          }
        },
        "type": "object",
        "required": [
          "sourceType",
          "pageCount"
        ],
        "title": "ExtractionReportResponse",
        "description": "Extraction report in API response format.\n\nContains metadata about how invoice data was extracted from the source PDF.\n\nAttributes:\n    source_type: How data was extracted (native_text or scan_ocr).\n    page_count: Total number of pages in source PDF.\n    page_confidences: Per-page OCR confidence scores (only for scan_ocr).\n    overall_confidence: Average OCR confidence percentage (only for scan_ocr).\n    ocr_processing_time_ms: Time spent on OCR in milliseconds."
      },
      "SkippedValidationProfileResponse": {
        "properties": {
          "profile": {
            "type": "string",
            "enum": [
              "facturx_en16931_base",
              "facturx_extended",
              "fr_ctc",
              "chorus_b2g"
            ],
            "title": "Profile"
          },
          "reason": {
            "type": "string",
            "enum": [
              "feature_flag_disabled",
              "artifact_unavailable",
              "runtime_not_implemented",
              "artifact_runtime_error",
              "jurisdiction_not_applicable",
              "not_requested",
              "not_attempted"
            ],
            "title": "Reason"
          }
        },
        "type": "object",
        "required": [
          "profile",
          "reason"
        ],
        "title": "SkippedValidationProfileResponse",
        "description": "One ``skippedValidationProfiles`` entry.\n\n``profile`` and ``reason`` are closed enums — invalid values are\nrejected at validation time so the wire payload never carries a\nfree-form string (\"ready_pa\", \"accepted_dgfip\", a typo, …)."
      },
      "UnsupportedValidationProfileResponse": {
        "properties": {
          "profile": {
            "type": "string",
            "enum": [
              "facturx_en16931_base",
              "facturx_extended",
              "fr_ctc",
              "chorus_b2g"
            ],
            "title": "Profile"
          },
          "reason": {
            "type": "string",
            "enum": [
              "feature_flag_disabled",
              "artifact_unavailable",
              "runtime_not_implemented",
              "artifact_runtime_error",
              "jurisdiction_not_applicable",
              "not_requested",
              "not_attempted"
            ],
            "title": "Reason"
          }
        },
        "type": "object",
        "required": [
          "profile",
          "reason"
        ],
        "title": "UnsupportedValidationProfileResponse",
        "description": "One ``unsupportedValidationProfiles`` entry. Stays empty in PR 2.\n\nSame closed-enum guarantees as :class:`SkippedValidationProfileResponse`."
      },
      "ValidationArtifactResponse": {
        "properties": {
          "id": {
            "type": "string",
            "title": "Id"
          },
          "family": {
            "type": "string",
            "title": "Family"
          },
          "profile": {
            "type": "string",
            "enum": [
              "facturx_en16931_base",
              "facturx_extended",
              "fr_ctc",
              "chorus_b2g"
            ],
            "title": "Profile"
          },
          "syntax": {
            "type": "string",
            "enum": [
              "cii",
              "ubl",
              "cdar"
            ],
            "title": "Syntax",
            "description": "``cii`` | ``ubl`` | ``cdar``"
          },
          "version": {
            "type": "string",
            "title": "Version"
          },
          "sourceAuthority": {
            "type": "string",
            "title": "Sourceauthority"
          },
          "sha256": {
            "type": "string",
            "pattern": "^[0-9a-f]{64}$",
            "title": "Sha256",
            "description": "64-character lowercase hex sha256"
          },
          "status": {
            "type": "string",
            "enum": [
              "available",
              "runtime_pending",
              "disabled"
            ],
            "title": "Status",
            "description": "``available`` | ``runtime_pending`` | ``disabled``"
          }
        },
        "type": "object",
        "required": [
          "id",
          "family",
          "profile",
          "syntax",
          "version",
          "sourceAuthority",
          "sha256",
          "status"
        ],
        "title": "ValidationArtifactResponse",
        "description": "One ``validationArtifacts`` entry — provenance of a Schematron / XSLT\npack that participated in validation.\n\n``profile`` / ``syntax`` / ``status`` are closed enums; ``sha256`` is\na strict 64-char lowercase hex pattern. The pattern shows up in the\nOpenAPI schema so generated clients can validate against it."
      },
      "ValidationReadinessSectionResponse": {
        "properties": {
          "status": {
            "type": "string",
            "title": "Status",
            "description": "Layer status. The runtime emits one of the values declared by ``ReadinessStatus`` in ``app/domain/services/product_truth_contract.py``: today the closed set is ``passed`` / ``blocked`` / ``not_run`` / ``not_evaluated`` / ``not_applicable`` / ``unknown`` / ``not_submitted`` / ``accepted_user_declared`` / ``accepted_verified``. The OpenAPI typing keeps the field as ``str`` so the engine can introduce a new evidence-bearing value without a breaking schema bump; the contract test ``test_openapi_validation_readiness_examples_match_runtime_status_set`` pins every example to the runtime set. France-first claim policy: ``passed`` MUST be backed by an evaluated stage — see ``docs/VALIDATION_TOPOLOGY.md`` §7."
          },
          "blockers": {
            "items": {
              "type": "string"
            },
            "type": "array",
            "title": "Blockers",
            "description": "Stable rule_id / blocker_id list backing the status. Empty when ``status`` is ``passed`` / ``not_evaluated`` / ``not_submitted``. Always equals ``blockerCount`` in length."
          },
          "blockerCount": {
            "type": "integer",
            "minimum": 0,
            "title": "Blockercount",
            "description": "Cached ``len(blockers)`` so SDKs do not have to re-count. Always equal to the length of ``blockers``.",
            "default": 0
          },
          "evidence": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Evidence",
            "description": "Short evidence token explaining how the status was produced (e.g. ``factur_x_1_08_extensions_not_runtime_observed``, ``pa_lifecycle_not_part_of_validation_target``)."
          },
          "reason": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Reason",
            "description": "Human-readable reason for ``not_evaluated`` / ``blocked`` states. Absent when the layer evaluated cleanly."
          },
          "target": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Target",
            "description": "Sub-target of the layer when relevant — e.g. ``pdf_a3`` for ``packaging`` or ``factur_x_1_08_extensions`` for ``facturxProfile``."
          },
          "sourcePresent": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ],
            "title": "Sourcepresent",
            "description": "True only when this readiness layer is backed by an actual source that reached runtime validation. For ``en16931`` this means a submitted XML source was available (direct XML upload or extractible embedded Factur-X XML) and at least one XSD or Schematron stage actually ran on that source; generated XML from native-PDF extraction and skipped placeholder stages do not set this flag."
          },
          "diagnosticOnly": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ],
            "title": "Diagnosticonly",
            "description": "When true, this layer reports diagnostic truth about the submitted source document and must not be converted by clients into a runnable /convert blocker or api_gap by itself."
          },
          "diagnosticScope": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Diagnosticscope",
            "description": "Scope of a diagnostic-only layer, for example ``submitted_embedded_xml`` when EN16931 was evaluated on the XML embedded in an uploaded PDF while PDF/A-3 packaging remains a separate readiness axis."
          }
        },
        "additionalProperties": true,
        "type": "object",
        "required": [
          "status"
        ],
        "title": "ValidationReadinessSectionResponse",
        "description": "One readiness layer (``en16931``, ``facturxProfile``,\n``frCtc``, ``paAcceptance``, or ``packaging``).\n\nMirrors ``ValidationReadinessSection`` in\n``app/domain/services/product_truth_contract.py:to_dict``. The\nruntime is authoritative for the value space — this DTO only\npublishes the shape and the camelCase field names.",
        "examples": [
          {
            "blockerCount": 0,
            "blockers": [],
            "evidence": "en16931_base_passed",
            "sourcePresent": true,
            "status": "passed"
          },
          {
            "blockerCount": 0,
            "blockers": [],
            "evidence": "factur_x_1_08_extensions_not_runtime_observed",
            "reason": "factur_x_1_08_profile_schematron_not_runtime_observable_for_en16931",
            "status": "not_evaluated",
            "target": "factur_x_1_08_extensions"
          },
          {
            "blockerCount": 0,
            "blockers": [],
            "evidence": "pa_lifecycle_not_part_of_validation_target",
            "reason": "pa_lifecycle_not_part_of_validation_target",
            "status": "not_submitted",
            "target": "not_submitted"
          }
        ]
      },
      "ProductTruthConversionInputResponse": {
        "properties": {
          "fieldPath": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Fieldpath",
            "description": "Logical invoice field path when the blocker maps to a concrete business field."
          },
          "label": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Label",
            "description": "Human label for the required input, fix or blocker."
          },
          "source": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Source",
            "description": "Evidence source, for example candidate_validation, document or user_input."
          },
          "status": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Status",
            "description": "Product Truth item status such as needs_input, automatic_fix or unsupported."
          },
          "reason": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Reason",
            "description": "Why the item is required, automatically handled or unsupported."
          },
          "supportedByCurrentApi": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ],
            "title": "Supportedbycurrentapi",
            "description": "Whether the current public API can complete or handle this item."
          },
          "btCode": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Btcode"
          },
          "ruleRef": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Ruleref"
          },
          "invoiceDataPath": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Invoicedatapath"
          },
          "patchPath": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Patchpath"
          },
          "requestContextPath": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Requestcontextpath"
          },
          "profilePath": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Profilepath"
          },
          "detectedValue": {
            "anyOf": [
              {},
              {
                "type": "null"
              }
            ],
            "title": "Detectedvalue"
          },
          "apiGap": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Apigap"
          },
          "supportKind": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Supportkind"
          }
        },
        "additionalProperties": true,
        "type": "object",
        "title": "ProductTruthConversionInputResponse",
        "description": "Structured Product Truth input/action item.\n\nThe target preflight contract reuses the same item family for\nrequired caller inputs, automatic fixes and unsupported blockers.\nSome automatic-fix entries are rule-level rather than field-level,\nso field-specific paths are optional while the schema still exposes\nthe stable Product Truth keys generated clients need."
      },
      "ValidationArtifact": {
        "properties": {
          "kind": {
            "type": "string",
            "title": "Kind",
            "description": "Artefact kind (xsd, schematron, xslt, xsl)"
          },
          "path": {
            "type": "string",
            "title": "Path",
            "description": "Path inside the application package"
          },
          "sha256": {
            "type": "string",
            "title": "Sha256"
          },
          "size_bytes": {
            "type": "integer",
            "title": "Size Bytes"
          },
          "version_declared": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Version Declared"
          },
          "version_detected": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Version Detected"
          },
          "origin": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Origin"
          }
        },
        "type": "object",
        "required": [
          "kind",
          "path",
          "sha256",
          "size_bytes"
        ],
        "title": "ValidationArtifact"
      },
      "PdfaEngineInfo": {
        "properties": {
          "provider": {
            "type": "string",
            "title": "Provider"
          },
          "configured": {
            "type": "boolean",
            "title": "Configured"
          },
          "version": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Version"
          }
        },
        "type": "object",
        "required": [
          "provider",
          "configured"
        ],
        "title": "PdfaEngineInfo"
      }
    }
  }
}
