openapi: 3.1.0
info:
  title: Optlyx Partner API — Autofatture Estere
  version: "0.1.0"
  contact:
    name: Optlyx
    email: hello@optlyx.com
  x-logo:
    url: ""
    altText: Optlyx
  description: |
    Motore di **conversione documenti esteri → XML FatturaPA**. Invii il PDF di una
    fattura estera (es. Anthropic, OpenAI, AWS); l'API fa OCR + AI, riconosce che è
    estera, valida i dati (VIES, valuta, importi) e genera l'**XML FatturaPA
    dell'autofattura pronto all'invio**, con il codice TD corretto.

    ## Confine di responsabilità

    | Optlyx (questa API) | Recivu / cliente finale |
    |---|---|
    | OCR, AI, riconoscimento, validazione, **generazione XML** | firma + **invio a SDI**, ricevute, conservazione |

    Optlyx è un **puro processore dati (OCR/AI)**: non tocca il canale SDI. Il titolare
    fiscale e il soggetto che trasmette è sempre l'azienda cliente finale.
    L'XML restituito è **non firmato**: firma (CAdES `.p7m`) e invio a SDI sono a carico
    del partner. La conservazione potrà arrivare come add-on Optlyx (fuori da questa versione).

    ## Autenticazione

    API key per partner nell'header `Authorization: Bearer pk_live_…` (produzione) o
    `pk_test_…` (sandbox). La key è "super-partner": agisce per conto di più aziende;
    l'azienda target va indicata nel campo `studioId` del body di ogni chiamata. Una key non
    abilitata per quello `studioId` riceve `403 TENANT_FORBIDDEN`.

    ## Idempotenza

    Su tutte le `POST` puoi passare l'header `Idempotency-Key: <uuid>`: ripetere la stessa
    richiesta con la stessa key restituisce la risposta originale senza rilavorare il
    documento (retry di rete sicuri).

    ## Rate limit

    60 richieste/minuto per API key. Oltre soglia: `429 RATE_LIMITED` con
    `details.retry_after_ms`.

    ## Soglia di confidenza

    Se l'OCR ha confidenza `< 0.95` (o emergono `blockingIssues`), la risposta ha
    `status: "needs_review"` e **non** viene prodotto un XML autoritativo: vanno mostrati
    i campi all'operatore per conferma.

    ## Codici TD gestiti

    - `TD17` — integrazione/autofattura per **servizi** da fornitore estero (obbligatoria, reverse charge)
    - `TD18` — acquisto **intracomunitario di beni** (UE)
    - `TD19` — acquisto beni ex art.17 c.2 (fornitore estero, beni già in IT; incl. San Marino)
    - `TD01` — fattura ordinaria, companion volontario per la contabilizzazione automatica

    ## Webhook

    Registra un endpoint HTTPS con `POST /v1/webhooks { "url": "https://…" }`: ricevi un
    `secret`. Ad ogni autofattura creata inviamo un evento `autofattura.created` firmato
    (header `X-Optlyx-Signature` = HMAC-SHA256 del body con il secret).

    ## Stato degli endpoint

    **Disponibili**: `documents:recognize`, `autofatture:process`, `autofatture` (create),
    `GET /autofatture`, `GET /autofatture/{id}`, `GET /autofatture/{id}/courtesy-pdf`,
    `POST /webhooks`. **Pianificato** (`🔜`): `:corrections` (learning).

servers:
  - url: https://europe-west1-playground-optlyx.cloudfunctions.net/partnerApi/v1
    description: Produzione / Sandbox (ambiente determinato dal prefisso della API key)

security:
  - ApiKeyAuth: []

tags:
  - name: documents
    description: Riconoscimento e anteprima dei documenti esteri.
  - name: autofatture
    description: Generazione dell'XML FatturaPA e gestione delle autofatture.

paths:
  /documents:recognize:
    post:
      tags: [documents]
      summary: Riconosci una fattura estera e suggerisci l'autofattura
      operationId: recognizeDocument
      description: |
        **✅ Disponibile.** Carichi un PDF, ottieni dati estratti, confidence e TD
        suggerito. Non genera XML: è lo step di anteprima/validazione. Se
        `status: "needs_review"` mostra i campi all'operatore prima di procedere.
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/RecognizeRequest' }
            examples:
              anthropic:
                summary: Fattura servizi USA
                value:
                  studioId: azienda-123
                  fileBase64: JVBERi0xLjQK... (PDF in base64)
                  mimeType: application/pdf
                  fileName: anthropic.pdf
      responses:
        '200':
          description: Documento riconosciuto
          content:
            application/json:
              schema: { $ref: '#/components/schemas/RecognizeResponse' }
              examples:
                ok:
                  summary: Riconosciuto con alta confidenza
                  value:
                    status: ok
                    confidence: 0.97
                    suggestedTd: TD17
                    tdReason: servizi da fornitore extra-UE
                    extracted:
                      supplierName: Anthropic PBC
                      country: US
                      currency: USD
                      invoiceNumber: INV-2026-42
                      invoiceDate: "2026-05-31"
                    vies: { valid: true }
                    blockingIssues: []
                needsReview:
                  summary: Sotto soglia → revisione manuale
                  value:
                    status: needs_review
                    confidence: 0.62
                    suggestedTd: TD17
                    extracted: { supplierName: "AC?E Ltd" }
                    blockingIssues: ["P.IVA fornitore illeggibile"]
        '400': { $ref: '#/components/responses/Error' }
        '401': { $ref: '#/components/responses/Error' }
        '403': { $ref: '#/components/responses/Error' }
        '422': { $ref: '#/components/responses/Error' }
        '429': { $ref: '#/components/responses/Error' }
      x-codeSamples:
        - lang: cURL
          source: |
            curl -X POST '.../partnerApi/v1/documents:recognize' \
              -H 'Authorization: Bearer pk_test_xxx' \
              -H 'Content-Type: application/json' \
              -d '{"studioId":"azienda-123","fileBase64":"<base64>","mimeType":"application/pdf"}'

  /autofatture:process:
    post:
      tags: [autofatture]
      summary: Pipeline completa PDF → XML FatturaPA pronto all'invio
      operationId: processAutofattura
      description: |
        **✅ Disponibile.** Il "fai tutto": carichi il PDF e ottieni l'autofattura con
        l'XML FatturaPA pronto all'invio. Incapsula l'intero motore (OCR → AI →
        riconoscimento → validazione → XML). Se non passi `cessionario`, vengono usati
        i dati dell'azienda (tenant). Sotto soglia di confidenza torna `needs_review` senza XML.
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/ProcessRequest' }
            examples:
              full:
                summary: Process con cessionario esplicito
                value:
                  studioId: azienda-123
                  fileBase64: JVBERi0xLjQK... (PDF in base64)
                  mimeType: application/pdf
                  fileName: anthropic.pdf
                  cessionario:
                    denominazione: Alfa S.r.l.
                    partitaIva: "09876543210"
                    sede: { nazione: IT }
                  options: { generateTd01: true, requireConfidence: 0.95 }
      responses:
        '200':
          description: Autofattura generata (oppure needs_review)
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AutofatturaResponse' }
              examples:
                ok:
                  value:
                    status: ok
                    autofatturaId: AF/2026/00001
                    td: TD17
                    confidence: 0.97
                    xml: "<?xml version=\"1.0\"…>"
                    companionTd01Xml: "<?xml…>"
                    importoTotaleCents: 12200
        '400': { $ref: '#/components/responses/Error' }
        '401': { $ref: '#/components/responses/Error' }
        '403': { $ref: '#/components/responses/Error' }
        '422': { $ref: '#/components/responses/Error' }
        '429': { $ref: '#/components/responses/Error' }
      x-codeSamples:
        - lang: cURL
          source: |
            curl -X POST '.../partnerApi/v1/autofatture:process' \
              -H 'Authorization: Bearer pk_test_xxx' \
              -H 'Content-Type: application/json' \
              -H 'Idempotency-Key: 7f3c-...' \
              -d '{"studioId":"azienda-123","fileBase64":"<base64>","mimeType":"application/pdf"}'

  /autofatture:
    post:
      tags: [autofatture]
      summary: Crea un'autofattura da dati strutturati
      operationId: createAutofattura
      description: |
        **✅ Disponibile.** Se hai già i dati strutturati (estratti e corretti
        dall'operatore), generi l'XML senza ripassare dall'OCR.
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/CreateAutofatturaRequest' }
            examples:
              td17:
                value:
                  studioId: azienda-123
                  fatturaData:
                    tipo: TD17
                    fornitoreEstero: { denominazione: Anthropic PBC, paese: US, idFiscale: US123456 }
                    linee:
                      - descrizione: API usage
                        quantita: 1
                        prezzoUnitario: 100
                        aliquotaIva: 22
                        natura: N6.2
                    valuta: EUR
                    data: "2026-05-31"
      responses:
        '200':
          description: Autofattura generata
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AutofatturaResponse' }
        '400': { $ref: '#/components/responses/Error' }
        '401': { $ref: '#/components/responses/Error' }
        '403': { $ref: '#/components/responses/Error' }
        '422': { $ref: '#/components/responses/Error' }
    get:
      tags: [autofatture]
      summary: Lista autofatture per azienda
      operationId: listAutofatture
      description: "**✅ Disponibile.** Lista/recupero delle autofatture convertite."
      parameters:
        - in: query
          name: studioId
          required: true
          schema: { type: string }
        - in: query
          name: page
          schema: { type: integer, default: 1 }
      responses:
        '200':
          description: Lista paginata
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: '#/components/schemas/AutofatturaResponse' }
                  page: { type: integer }
                  hasMore: { type: boolean }
        '401': { $ref: '#/components/responses/Error' }
        '403': { $ref: '#/components/responses/Error' }

  /autofatture/{id}:
    get:
      tags: [autofatture]
      summary: Recupera un'autofattura e il suo XML
      operationId: getAutofattura
      description: "**✅ Disponibile.** Lista/recupero delle autofatture convertite."
      parameters:
        - $ref: '#/components/parameters/AutofatturaId'
        - in: query
          name: studioId
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Autofattura
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AutofatturaResponse' }
        '401': { $ref: '#/components/responses/Error' }
        '403': { $ref: '#/components/responses/Error' }
        '404': { $ref: '#/components/responses/Error' }

  /autofatture/{id}/courtesy-pdf:
    get:
      tags: [autofatture]
      summary: PDF di cortesia (non fiscale) generato dall'XML
      operationId: getCourtesyPdf
      description: "**✅ Disponibile.** PDF di cortesia (non fiscale) generato dai dati."
      parameters:
        - $ref: '#/components/parameters/AutofatturaId'
        - in: query
          name: studioId
          required: true
          schema: { type: string }
      responses:
        '200':
          description: PDF in base64
          content:
            application/json:
              schema:
                type: object
                properties:
                  pdfBase64: { type: string }
        '404': { $ref: '#/components/responses/Error' }

  /autofatture/{id}:corrections:
    post:
      tags: [autofatture]
      summary: Invia correzioni dell'operatore (learning)
      operationId: submitCorrections
      description: "**🔜 In arrivo (Sprint 3).** Le correzioni alimentano la memoria di estrazione per fornitore."
      parameters:
        - $ref: '#/components/parameters/AutofatturaId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [studioId, corrections]
              properties:
                studioId: { type: string }
                corrections: { type: object, additionalProperties: true }
      responses:
        '200':
          description: Correzioni registrate
          content:
            application/json:
              schema:
                type: object
                properties: { ok: { type: boolean } }
        '401': { $ref: '#/components/responses/Error' }
        '403': { $ref: '#/components/responses/Error' }

  /webhooks:
    post:
      tags: [autofatture]
      summary: Registra l'endpoint webhook del partner
      operationId: registerWebhook
      description: |
        **✅ Disponibile.** Registra un URL HTTPS che riceverà gli eventi
        `autofattura.created`, firmati con HMAC-SHA256 (header `X-Optlyx-Signature`).
        La risposta contiene il `secret` con cui verificare la firma.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [url]
              properties:
                url: { type: string, format: uri, example: "https://recivu.it/webhooks/optlyx" }
      responses:
        '200':
          description: Webhook registrato
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
                  secret: { type: string, description: "Secret HMAC — salvalo per verificare le firme." }
        '400': { $ref: '#/components/responses/Error' }
        '401': { $ref: '#/components/responses/Error' }

components:
  securitySchemes:
    ApiKeyAuth:
      type: http
      scheme: bearer
      description: |
        `Authorization: Bearer pk_live_…` (produzione) o `pk_test_…` (sandbox). La key
        agisce per più studi; lo studio target va nel campo `studioId` del body.

  parameters:
    IdempotencyKey:
      in: header
      name: Idempotency-Key
      required: false
      schema: { type: string }
      description: Stessa key → stessa risposta cache-ata (retry sicuri).
    AutofatturaId:
      in: path
      name: id
      required: true
      schema: { type: string }
      description: Identificativo dell'autofattura (es. `AF/2026/00001`).

  responses:
    Error:
      description: Errore normalizzato
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
          examples:
            forbidden:
              value: { error: { code: TENANT_FORBIDDEN, message: "API key not authorized for this studio", details: { studioId: studio-9 } } }

  schemas:
    Error:
      type: object
      description: Envelope di errore. `code` è stabile e gestibile programmaticamente.
      properties:
        error:
          type: object
          required: [code, message]
          properties:
            code:
              type: string
              description: |
                Codice errore stabile:
                `BAD_REQUEST` (400), `UNAUTHORIZED` (401), `TENANT_FORBIDDEN` (403),
                `NOT_FOUND` (404), `IDEMPOTENCY_CONFLICT` (409), `OCR_LOW_CONFIDENCE` (422),
                `INVALID_TD` (422), `VIES_FAILED` (422), `QUOTA_EXCEEDED` (429),
                `RATE_LIMITED` (429), `INTERNAL` (500).
              enum: [BAD_REQUEST, UNAUTHORIZED, TENANT_FORBIDDEN, NOT_FOUND, IDEMPOTENCY_CONFLICT, OCR_LOW_CONFIDENCE, INVALID_TD, VIES_FAILED, QUOTA_EXCEEDED, RATE_LIMITED, INTERNAL]
            message: { type: string, description: Messaggio umano-leggibile (non per il parsing). }
            details: { type: object, additionalProperties: true, description: Contesto opzionale (es. retry_after_ms, studioId). }

    ExtractedFields:
      type: object
      description: Campi estratti dal documento dall'OCR/AI. `confidence` ∈ [0,1].
      properties:
        confidence: { type: number, format: float, example: 0.97 }
        supplierName: { type: string, example: Anthropic PBC }
        country: { type: string, description: ISO2, example: US }
        vatId: { type: string, example: US123456 }
        currency: { type: string, example: USD }
        invoiceNumber: { type: string, example: INV-2026-42 }
        invoiceDate: { type: string, format: date, example: "2026-05-31" }
        subtotal: { type: number, example: 100 }
        items:
          type: array
          items:
            type: object
            properties:
              description: { type: string }
              quantity: { type: number }
              unitPrice: { type: number }
              amount: { type: number }
      additionalProperties: true

    RecognizeRequest:
      type: object
      required: [studioId, fileBase64, mimeType]
      properties:
        studioId: { type: string, description: Studio per conto del quale si opera (nello scope della key). }
        fileBase64: { type: string, description: PDF o immagine codificato base64. }
        mimeType: { type: string, example: application/pdf }
        fileName: { type: string, example: anthropic.pdf }

    RecognizeResponse:
      type: object
      properties:
        status: { type: string, enum: [ok, needs_review], description: "`needs_review` se confidence < 0.95 o blockingIssues presenti." }
        confidence: { type: number, format: float }
        suggestedTd: { type: string, nullable: true, enum: [TD17, TD18, TD19, TD01], example: TD17 }
        tdReason: { type: string, nullable: true }
        extracted: { $ref: '#/components/schemas/ExtractedFields' }
        isCreditNote: { type: boolean }
        isTaxRefund: { type: boolean }
        validationWarnings: { type: array, items: { type: string } }
        blockingIssues: { type: array, items: { type: string } }
        vies: { type: object, nullable: true, additionalProperties: true }

    ProcessRequest:
      type: object
      required: [studioId, fileBase64, mimeType]
      properties:
        studioId: { type: string }
        fileBase64: { type: string }
        mimeType: { type: string, example: application/pdf }
        fileName: { type: string }
        cessionario:
          $ref: '#/components/schemas/Cessionario'
          description: Cliente italiano dell'autofattura. Se omesso, vengono usati i dati dell'azienda.
        options:
          type: object
          properties:
            generateTd01: { type: boolean, default: true, description: Genera anche il companion TD01. }
            requireConfidence: { type: number, default: 0.95, description: Soglia minima per emettere senza revisione. }

    Cessionario:
      type: object
      description: Cessionario/committente italiano (CessionarioCommittente nell'XML).
      properties:
        denominazione: { type: string }
        partitaIva: { type: string }
        codiceFiscale: { type: string }
        regimeFiscale: { type: string, example: RF01 }
        sede:
          type: object
          properties:
            indirizzo: { type: string }
            cap: { type: string }
            comune: { type: string }
            provincia: { type: string }
            nazione: { type: string, example: IT }
        codiceDestinatario: { type: string, example: "0000000" }
        pec: { type: string }

    FatturaData:
      type: object
      description: Dati strutturati per generare l'autofattura senza OCR.
      required: [tipo]
      properties:
        tipo: { type: string, enum: [TD17, TD18, TD19, TD01] }
        generateTd01: { type: boolean, default: true }
        fornitoreEstero:
          type: object
          properties:
            denominazione: { type: string }
            idFiscale: { type: string }
            paese: { type: string }
            sede:
              type: object
              properties:
                indirizzo: { type: string }
                cap: { type: string }
                comune: { type: string }
        cessionario: { $ref: '#/components/schemas/Cessionario' }
        fatturaOriginale:
          type: object
          properties:
            numero: { type: string }
            data: { type: string, format: date }
        linee:
          type: array
          items:
            type: object
            properties:
              descrizione: { type: string }
              quantita: { type: number, default: 1 }
              prezzoUnitario: { type: number }
              aliquotaIva: { type: number, example: 22 }
              natura: { type: string, example: N6.2 }
        valuta: { type: string, example: EUR }
        data: { type: string, format: date }

    CreateAutofatturaRequest:
      type: object
      required: [studioId, fatturaData]
      properties:
        studioId: { type: string }
        fatturaData: { $ref: '#/components/schemas/FatturaData' }

    AutofatturaResponse:
      type: object
      properties:
        status: { type: string, enum: [ok, needs_review] }
        autofatturaId: { type: string, example: AF/2026/00001 }
        td: { type: string, enum: [TD17, TD18, TD19, TD01] }
        confidence: { type: number, format: float }
        extracted: { $ref: '#/components/schemas/ExtractedFields' }
        xml: { type: string, description: XML FatturaPA 1.2 NON firmato — firma e invia a SDI dal tuo canale. }
        companionTd01Xml: { type: string, nullable: true }
        importoTotaleCents: { type: integer, description: Totale documento in centesimi., example: 12200 }
        blockingIssues: { type: array, items: { type: string } }
