openapi: 3.1.0 info: title: ZIA Translation Service description: > Service de traduction de documents. Accepte un fichier PDF en entrée, en extrait le texte via OCR (modèle vision), et le traduit via un LLM. Retourne soit un job PDF asynchrone, soit un flux SSE de pages traduites. version: 0.0.1 servers: - url: /api/translation description: Translation API base path paths: /pdf: post: summary: Submit an async PDF translation job operationId: translateToPdf requestBody: required: true content: multipart/form-data: schema: type: object properties: file: type: string format: binary description: Source PDF document targetLanguage: $ref: '#/components/schemas/TargetLanguage' strategy: $ref: '#/components/schemas/TranslationStrategy' responses: '202': description: Translation job accepted content: application/json: schema: $ref: '#/components/schemas/TranslationJobResponse' '400': description: Bad request — missing file, empty file, unsupported format, or unknown strategy value content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /pdf/{jobId}/status: get: summary: Get async PDF translation job status operationId: getPdfTranslationJobStatus deprecated: true description: > **Deprecated**: use `GET /jobs/{jobId}/status` instead. Kept for backward compatibility; behavior is unchanged. parameters: - in: path name: jobId required: true schema: type: string format: uuid responses: '200': description: Current job status content: application/json: schema: $ref: '#/components/schemas/TranslationJobResponse' '404': description: Unknown job identifier /pdf/{jobId}: get: summary: Download translated PDF when async job is complete operationId: downloadTranslatedPdf deprecated: true description: > **Deprecated**: use `GET /jobs/{jobId}` instead. Kept for backward compatibility; behavior is unchanged. parameters: - in: path name: jobId required: true schema: type: string format: uuid responses: '200': description: Translated PDF document content: application/pdf: schema: type: string format: binary '404': description: Unknown job identifier '409': description: Job is not completed yet (PENDING or PROCESSING) '410': description: Translated PDF file has expired and was cleaned up '422': description: Job failed during processing /md: post: summary: Submit an async Markdown translation job operationId: translateToMarkdown requestBody: required: true content: multipart/form-data: schema: type: object properties: file: type: string format: binary description: Source document targetLanguage: $ref: '#/components/schemas/TargetLanguage' strategy: $ref: '#/components/schemas/TranslationStrategy' responses: '202': description: Translation job accepted content: application/json: schema: $ref: '#/components/schemas/TranslationJobResponse' '400': description: Bad request — missing file, empty file, unsupported format, or unknown strategy value content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /jobs/{jobId}/status: get: summary: Get async translation job status (PDF or Markdown) operationId: getTranslationJobStatus parameters: - in: path name: jobId required: true schema: type: string format: uuid responses: '200': description: Current job status content: application/json: schema: $ref: '#/components/schemas/TranslationJobResponse' '404': description: Unknown job identifier /jobs/{jobId}: get: summary: Download translated file when async job is complete (PDF or Markdown) operationId: downloadTranslatedFile description: > The response `Content-Type` and file extension depend on the job's output format: `application/pdf` (`.pdf`) for a PDF job, `text/markdown` (`.md`) for a Markdown job. parameters: - in: path name: jobId required: true schema: type: string format: uuid responses: '200': description: Translated file (PDF or Markdown depending on the job's output format) content: application/pdf: schema: type: string format: binary text/markdown: schema: type: string '404': description: Unknown job identifier '409': description: Job is not completed yet (PENDING or PROCESSING) '410': description: Translated file has expired and was cleaned up '422': description: Job failed during processing /text: post: summary: Translate a PDF document and stream translated pages as SSE operationId: translateToText requestBody: required: true content: multipart/form-data: schema: type: object properties: file: type: string format: binary description: Source PDF document targetLanguage: $ref: '#/components/schemas/TargetLanguage' strategy: $ref: '#/components/schemas/TranslationStrategy' responses: '200': description: > Server-Sent Events stream. Emits `page` events with TranslationPageEvent payload, then a `complete` event with TranslationCompleteEvent payload. Emits `error` with a message payload if processing fails mid-stream. content: text/event-stream: schema: type: string examples: pageAndComplete: summary: Example stream sequence value: | event: page data: {"pageNumber":1,"text":"Texte traduit de la page 1..."} event: complete data: {"totalPages":1} '400': description: Bad request — missing file, empty file, unsupported format, or unknown strategy value content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /plain-text: post: summary: Synchronously translate a plain text (no file, no job, no streaming) operationId: translatePlainText requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/PlainTextTranslationRequest' responses: '200': description: Translated text content: application/json: schema: $ref: '#/components/schemas/PlainTextTranslationResponse' '400': description: Bad request — missing/unreadable body, missing or empty `text`, or `text` too long content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '415': description: Unsupported Content-Type (must be `application/json`) content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '422': description: Translation failed on the LLM side content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /extractions: servers: - url: /api/v1 description: Extraction API base path post: summary: Submit the extraction of values from a document (async, vision model, structured output) operationId: submitExtraction description: > Generic extraction of values from a document (PDF or image), e.g. used by `ole-sumex-invoice-parser` with the `sumex-invoice` profile when classic extraction is incomplete. Each page is analysed by the internal vision model, whose answer is constrained by a JSON Schema generated by the service from the requested keys (`context.missingFields`) and their definition in the profile (see `/extraction-profiles`). Without profile, every key is extracted as free text. Values are normalized (dates `yyyy-MM-dd`, amounts/numbers with `.`, identifiers without separators) and the confidence of the model is capped when no `evidence` is given or when the value is not found in its evidence. The value with the highest confidence is kept for each key. When the profile classifies its pages (e.g. `sumex-invoice`) or when `context.pageHints` is given, the pages are first classified (`MAIN`: the one invoice of the file; `SUPPORTING`: supporting documents; `OTHER`: blank or unreadable pages): pages with a hint are not classified again, the other pages are classified one by one by the vision model. The values are then extracted from the pages of the main document only, sent together (at most `zia.extraction.max-images-per-call` images per call), unclassified pages being treated as pages of the main document; without main document, all the pages except `OTHER` ones are analysed. The classification of the pages is returned in `result.pages`. Idempotent on `Idempotency-Key`: a known key returns the existing job. Jobs are kept `zia.extraction.job-ttl` (default 24h) after their completion. parameters: - in: header name: Idempotency-Key required: true schema: type: string maxLength: 128 requestBody: required: true content: multipart/form-data: schema: type: object required: [file, context] properties: file: type: string format: binary description: The document (PDF, PNG, JPEG, GIF or BMP) context: $ref: '#/components/schemas/ExtractionContext' encoding: context: contentType: application/json responses: '202': description: Job created (or existing job for this `Idempotency-Key`) headers: Location: description: URL of the job, relative to the submission URL (`extractions/{jobId}`) schema: type: string Retry-After: description: Seconds to wait before polling the job schema: type: integer content: application/json: schema: $ref: '#/components/schemas/ExtractionJob' '400': description: > Missing/invalid `Idempotency-Key`, `file` or `context`, unsupported document type, unknown profile, no extractable key, too many keys (`zia.extraction.max-fields`) or instructions too long content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '413': description: Document too large '429': description: Too many extractions in progress (retry later) /extractions/{jobId}: servers: - url: /api/v1 description: Extraction API base path get: summary: Status of an extraction job and, once finished, its result operationId: getExtraction parameters: - in: path name: jobId required: true schema: type: string responses: '200': description: Status of the job; `Retry-After` is set while it is `PENDING` or `RUNNING` headers: Retry-After: schema: type: integer content: application/json: schema: $ref: '#/components/schemas/ExtractionJob' '404': description: Unknown or expired job /extraction-profiles: servers: - url: /api/v1 description: Extraction API base path get: summary: Extraction profiles known by the service operationId: listExtractionProfiles responses: '200': description: Profiles, sorted by id content: application/json: schema: type: array items: type: object properties: id: type: string description: type: string fieldCount: type: integer collections: type: array items: type: string /extraction-profiles/{profileId}: servers: - url: /api/v1 description: Extraction API base path get: summary: Keys (fields and collections) of an extraction profile operationId: getExtractionProfile parameters: - in: path name: profileId required: true schema: type: string responses: '200': description: The profile content: application/json: schema: $ref: '#/components/schemas/ExtractionProfile' '404': description: Unknown profile components: schemas: TranslationStrategy: type: string description: > Translation strategy to use for this request. If omitted, the global configuration (`zia.translation.strategy`) is used as a fallback. enum: - single - dual TargetLanguage: type: string description: > Target language. Supported languages: French (`fr`), German (`de`), Italian (`it`) and English (`en`). ISO 639-1/639-2 codes, regional variants (e.g. `de-CH`) and common language names in fr/de/it/en (e.g. `Deutsch`, `allemand`, `German`) are accepted, case- and accent-insensitive. If omitted, blank or unsupported, the default language (`zia.translation.default-language`, env `ZIA_TRANSLATION_DEFAULT_LANGUAGE`) is used. Generated PDFs include an AI-translation disclaimer footer in the resolved language. examples: - fr - de - it - en TranslationJobResponse: type: object required: - jobId - status properties: jobId: type: string format: uuid description: UUID v4 of the async translation job status: $ref: '#/components/schemas/JobStatus' JobStatus: type: string enum: - PENDING - PROCESSING - COMPLETED - FAILED TranslationPageEvent: type: object required: - pageNumber - text properties: pageNumber: type: integer text: type: string TranslationCompleteEvent: type: object required: - totalPages properties: totalPages: type: integer PlainTextTranslationRequest: type: object required: - text properties: text: type: string description: Source plain text to translate targetLanguage: $ref: '#/components/schemas/TargetLanguage' PlainTextTranslationResponse: type: object required: - translatedText properties: translatedText: type: string description: The translated plain text ErrorResponse: type: object description: Uniform error response body required: - status - message - timestamp properties: status: type: integer description: HTTP status code message: type: string description: Human-readable error description timestamp: type: string format: date-time description: Time at which the error occurred (ISO-8601) ExtractionContext: type: object required: [missingFields] properties: profile: type: string description: Id of a server-side extraction profile (e.g. `sumex-invoice`); ad hoc extraction when absent documentId: type: string language: type: string description: Expected language of the document (hint for the model) knownFields: type: object description: Values already known (key = field key), given to the model as hints additionalProperties: type: string missingFields: type: array minItems: 1 maxItems: 80 description: > Keys to extract. With a profile: keys of its fields and collections (e.g. `biller.gln`, `services`), unknown keys being ignored with a warning. Without profile: free keys matching `[A-Za-z0-9_][A-Za-z0-9_.-]{0,99}`, extracted as text. items: type: string instructions: type: string maxLength: 2000 description: Additional instructions for the model pageHints: type: array description: > Classification of pages already known by the client (e.g. from the text layer), kept as given: these pages are not classified again. Pages without hint are classified by the vision model. `confidence` and `evidence` are not expected. items: $ref: '#/components/schemas/PageClassification' example: - {page: 1, documentRole: MAIN, documentType: FORM, formId: '300.001'} - {page: 3, documentRole: SUPPORTING, documentType: INVOICE} PageClassification: type: object required: [page, documentRole] properties: page: type: integer minimum: 1 description: 1-based page of the file documentRole: type: string enum: [MAIN, SUPPORTING, OTHER] description: > `MAIN`: page of the invoice addressed to the insurance or to the insured person (source of the values); `SUPPORTING`: supporting document (e.g. invoice of a manufacturer addressed to the provider); `OTHER`: blank page, separator, unreadable page. documentType: type: string enum: [FORM, INVOICE, CREDIT_NOTE, REMINDER, QUOTE, PRESCRIPTION, REPORT, COVER_LETTER, POWER_OF_ATTORNEY, OTHER] description: Generic type of the document the page belongs to formId: type: string maxLength: 35 description: Identifier printed on a form (e.g. `300.001`), when the page belongs to a form confidence: type: number format: double minimum: 0 maximum: 1 evidence: type: string description: Text that justifies the classification (title, recipient...) ExtractionJob: type: object required: [jobId, status] properties: jobId: type: string status: type: string enum: [PENDING, RUNNING, SUCCEEDED, FAILED] submittedAt: type: string format: date-time result: $ref: '#/components/schemas/ExtractionResponse' error: type: object properties: code: type: string enum: [UNREADABLE_DOCUMENT, EXTRACTION_FAILED, INTERNAL_ERROR] message: type: string ExtractedField: type: object required: [value] properties: value: type: string description: Normalized value confidence: type: number minimum: 0 maximum: 1 description: Confidence computed by the service page: type: integer description: 1-based page where the value was read evidence: type: string description: Text read in the document by the model ExtractionResponse: type: object properties: profile: type: string documentType: type: string description: Free classification of the document (informative) pages: type: array description: > Classification of each page of the file, present when the pages are classified (unclassified pages are absent). The fields and collection rows then come from the `MAIN` pages only. items: $ref: '#/components/schemas/PageClassification' fields: type: object description: Values found, by key; a key that was not found is absent additionalProperties: $ref: '#/components/schemas/ExtractedField' collections: type: object description: Rows of each requested collection (row = values by field name) additionalProperties: type: array items: type: object additionalProperties: $ref: '#/components/schemas/ExtractedField' warnings: type: array items: type: string FieldDefinition: type: object properties: key: type: string type: type: string enum: [STRING, DATE, AMOUNT, NUMBER, IDENTIFIER, ENUM] description: type: string values: type: array description: Allowed values of an `ENUM` field items: type: string ExtractionProfile: type: object properties: id: type: string description: type: string fields: type: array items: $ref: '#/components/schemas/FieldDefinition' collections: type: array items: type: object properties: key: type: string description: type: string fields: type: array items: $ref: '#/components/schemas/FieldDefinition'