{
  "openapi": "3.1.0",
  "info": {
    "title": "NewEra SDI API",
    "version": "1.0.0",
    "summary": "Fatturazione elettronica verso il Sistema di Interscambio (SDI) per software esterni.",
    "description": "Canale di MORLAPPO S.r.l. (soggetto trasmittente, P.IVA 02174570677, canale web service accreditato con l'Agenzia delle Entrate) per inviare e ricevere fatture elettroniche per conto delle aziende dei partner.\n\nCosa fa l'API:\n- **invio**: il partner manda i dati della fattura in JSON (l'XML, i totali, l'IVA e il bollo li calcoliamo noi) oppure un XML FatturaPA 1.2.2 già pronto o un .p7m firmato; noi controlliamo, trasmettiamo allo SDI e seguiamo l'esito;\n- **esiti**: ogni notifica SDI (consegna, scarto, mancata consegna, esito PA, decorrenza termini) aggiorna lo stato della fattura e genera un evento, spedito come avviso firmato (webhook) e sempre leggibile con `GET /eventi`;\n- **ricezione**: le fatture dei fornitori destinate alle aziende del partner arrivano come documenti scaricabili.\n\nGuida discorsiva con esempi: `index.html` nella stessa cartella di questo file.\n\nConvenzioni: JSON UTF-8, nomi dei campi in italiano, date `AAAA-MM-GG`, importi in euro come numeri con il punto decimale, file binari in base64.",
    "contact": { "name": "MORLAPPO S.r.l. — assistenza NewEra SDI API", "email": "info@morlappo.com" }
  },
  "servers": [
    { "url": "https://api.neweraerp.it/api/v1", "description": "Unico indirizzo. L'ambiente (prova o produzione) lo decide la chiave: nea_test_… → SDI di test, nea_live_… → SDI reale" }
  ],
  "security": [{ "chiaveApi": [] }],
  "tags": [
    { "name": "Servizio", "description": "Verifica della chiave" },
    { "name": "Aziende", "description": "Le aziende (es. scuole) per cui il partner emette e riceve fatture" },
    { "name": "Fatture emesse", "description": "Invio allo SDI e stato delle fatture emesse" },
    { "name": "Firma", "description": "Fatture da firmare con la firma digitale del cliente (obbligatoria verso la PA)" },
    { "name": "Fatture ricevute", "description": "Fatture dei fornitori arrivate alle aziende del partner" },
    { "name": "Eventi", "description": "Cronologia di tutto ciò che accade ai documenti (rete di sicurezza degli avvisi)" }
  ],
  "paths": {
    "/ping": {
      "get": {
        "tags": ["Servizio"],
        "summary": "Verifica la chiave API",
        "operationId": "ping",
        "responses": {
          "200": {
            "description": "Chiave valida",
            "content": { "application/json": { "schema": {
              "type": "object",
              "properties": {
                "ok": { "type": "boolean", "const": true },
                "partner": { "type": "string", "examples": ["Scuole di Musica App"] },
                "ambiente": { "$ref": "#/components/schemas/Ambiente" }
              }
            } } }
          },
          "401": { "$ref": "#/components/responses/NonAutenticato" },
          "429": { "$ref": "#/components/responses/TroppeRichieste" }
        }
      }
    },
    "/aziende": {
      "get": {
        "tags": ["Aziende"],
        "summary": "Elenco delle aziende attivate dal partner",
        "operationId": "elencoAziende",
        "responses": {
          "200": {
            "description": "Aziende attivate",
            "content": { "application/json": { "schema": {
              "type": "object",
              "properties": { "aziende": { "type": "array", "items": { "$ref": "#/components/schemas/Azienda" } } }
            } } }
          },
          "401": { "$ref": "#/components/responses/NonAutenticato" }
        }
      },
      "post": {
        "tags": ["Aziende"],
        "summary": "Attiva (o aggiorna) un'azienda",
        "description": "Prima di emettere fatture per un'azienda la si attiva con i suoi dati anagrafici: diventano i dati del cedente in ogni fattura generata dal JSON. Ripetere la chiamata con la stessa partita IVA aggiorna i dati (risposta 200). Una partita IVA già presente sul nostro sistema per un altro soggetto viene rifiutata (409): le fatture ricevute si smistano per partita IVA e devono avere un solo destinatario.",
        "operationId": "attivaAzienda",
        "requestBody": {
          "required": true,
          "content": { "application/json": {
            "schema": { "$ref": "#/components/schemas/AziendaInput" },
            "example": {
              "partitaIva": "01234567890",
              "ragioneSociale": "Scuola di Musica Armonia APS",
              "regimeFiscale": "RF01",
              "indirizzo": "Via Verdi 3",
              "cap": "64100",
              "comune": "Teramo",
              "provincia": "TE",
              "email": "segreteria@scuolaarmonia.it"
            }
          } }
        },
        "responses": {
          "201": { "description": "Azienda attivata", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RispostaAzienda" } } } },
          "200": { "description": "Azienda già attiva: dati aggiornati", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RispostaAzienda" } } } },
          "400": { "$ref": "#/components/responses/RichiestaNonValida" },
          "401": { "$ref": "#/components/responses/NonAutenticato" },
          "409": { "description": "Partita IVA già presente sul sistema (codice PARTITA_IVA_IN_USO): contattare MORLAPPO", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Errore" } } } },
          "422": { "$ref": "#/components/responses/DatiNonValidi" }
        }
      }
    },
    "/fatture": {
      "post": {
        "tags": ["Fatture emesse"],
        "summary": "Invia una fattura",
        "description": "Il corpo contiene **uno solo** tra:\n- `fattura`: i dati in JSON (consigliato) — XML, imponibili, IVA, riepiloghi, totale, bollo e ritenuta li calcoliamo noi;\n- `fileXml`: XML FatturaPA 1.2.2 non firmato, in base64 (trasmittente e progressivo d'invio vengono sostituiti con quelli del nostro canale);\n- `fileP7m`: XML già firmato CAdES (.p7m), in base64 — trasmesso così com'è.\n\nPrima della trasmissione la fattura passa i nostri controlli: schema ufficiale FatturaPA 1.2.2, cedente tra le aziende attivate, coerenza formato/codice destinatario, data non futura, dimensione massima 5 MB. Se non li passa **non arriva allo SDI** (stato `RIFIUTATA_DA_NOI`, risposta 422 con l'elenco degli errori) e si può correggere e reinviare.\n\nLe fatture alla PA (formato FPA12) non firmate, e quelle inviate con `\"firma\": true`, restano in stato `DA_FIRMARE` (risposta 201): vedi i percorsi della sezione Firma.\n\nL'header `Idempotency-Key` è fortemente consigliato: se la risposta si perde (timeout, rete), ripetere la stessa richiesta con la stessa chiave restituisce il documento già creato (200) senza un secondo invio.",
        "operationId": "inviaFattura",
        "parameters": [{ "$ref": "#/components/parameters/IdempotencyKey" }],
        "requestBody": {
          "required": true,
          "content": { "application/json": {
            "schema": { "$ref": "#/components/schemas/RichiestaInvio" },
            "examples": {
              "json": {
                "summary": "Retta a privato: corso esente + iscrizione con IVA",
                "value": {
                  "fattura": {
                    "cedentePartitaIva": "01234567890",
                    "numero": "2026/0153",
                    "data": "2026-10-22",
                    "causale": "Retta ottobre 2026",
                    "cliente": {
                      "nome": "Mario", "cognome": "Rossi", "codiceFiscale": "RSSMRA80A01L103X",
                      "indirizzo": "Via Roma 1", "cap": "64100", "comune": "Teramo", "provincia": "TE"
                    },
                    "righe": [
                      { "descrizione": "Corso di pianoforte - ottobre", "prezzoUnitario": 120, "aliquotaIva": 0, "natura": "N4", "riferimentoNormativo": "Esente art. 10 c.1 n.20 DPR 633/72" },
                      { "descrizione": "Quota di iscrizione", "prezzoUnitario": 30, "aliquotaIva": 22 }
                    ],
                    "pagamento": { "modalita": "MP08" }
                  }
                }
              },
              "xml": { "summary": "XML FatturaPA già pronto", "value": { "fileXml": "PD94bWwgdmVyc2lvbj0iMS4wIi4uLg==" } },
              "p7m": { "summary": "Fattura PA già firmata", "value": { "fileP7m": "MIAGCSqGSIb3DQEHAqCAMIACAQEx..." } }
            }
          } }
        },
        "responses": {
          "201": {
            "description": "Fattura trasmessa allo SDI (stato INVIATA) oppure in attesa di firma (stato DA_FIRMARE)",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RispostaInvio" } } }
          },
          "200": {
            "description": "Idempotency-Key già usata: restituito il documento esistente, nessun nuovo invio",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RispostaInvio" } } }
          },
          "400": { "$ref": "#/components/responses/RichiestaNonValida" },
          "401": { "$ref": "#/components/responses/NonAutenticato" },
          "422": {
            "description": "Fattura non valida: NON trasmessa. Due forme: errori nei dati JSON (oggetto `errore`, codice FATTURA_NON_VALIDA, elenco in `dettagli`) oppure controlli sul file falliti (documento in stato RIFIUTATA_DA_NOI ed elenco in `errori`)",
            "content": { "application/json": { "schema": { "oneOf": [
              { "$ref": "#/components/schemas/Errore" },
              { "$ref": "#/components/schemas/RispostaInvio" }
            ] } } }
          },
          "429": { "$ref": "#/components/responses/TroppeRichieste" },
          "502": {
            "description": "Comunicazione con lo SDI interrotta: stato ESITO_INCERTO. NON reinviare: verifichiamo noi e lo stato si aggiorna con le notifiche (avviso / GET /eventi)",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RispostaInvio" } } }
          }
        }
      },
      "get": {
        "tags": ["Fatture emesse"],
        "summary": "Elenco delle fatture emesse",
        "description": "Dalla più recente. Per seguire i cambi di stato conviene usare gli avvisi o `GET /eventi` invece di interrogare questo elenco a intervalli.",
        "operationId": "elencoFatture",
        "parameters": [
          { "name": "stato", "in": "query", "schema": { "$ref": "#/components/schemas/StatoEmessa" } },
          { "name": "dal", "in": "query", "description": "Solo documenti creati da questa data", "schema": { "type": "string", "format": "date" } },
          { "name": "limite", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 200, "default": 50 } }
        ],
        "responses": {
          "200": { "description": "Fatture", "content": { "application/json": { "schema": {
            "type": "object", "properties": { "fatture": { "type": "array", "items": { "$ref": "#/components/schemas/Documento" } } }
          } } } },
          "400": { "$ref": "#/components/responses/RichiestaNonValida" },
          "401": { "$ref": "#/components/responses/NonAutenticato" }
        }
      }
    },
    "/fatture/{id}": {
      "get": {
        "tags": ["Fatture emesse"],
        "summary": "Stato di una fattura con la cronologia degli eventi",
        "operationId": "dettaglioFattura",
        "parameters": [{ "$ref": "#/components/parameters/Id" }],
        "responses": {
          "200": { "description": "Fattura ed eventi", "content": { "application/json": { "schema": {
            "type": "object",
            "properties": {
              "fattura": { "$ref": "#/components/schemas/Documento" },
              "eventi": { "type": "array", "items": { "$ref": "#/components/schemas/Evento" } }
            }
          } } } },
          "401": { "$ref": "#/components/responses/NonAutenticato" },
          "404": { "$ref": "#/components/responses/NonTrovata" }
        }
      }
    },
    "/fatture/{id}/file": {
      "get": {
        "tags": ["Fatture emesse", "Firma"],
        "summary": "Scarica il file della fattura",
        "description": "Il file come trasmesso allo SDI (XML o .p7m). Per una fattura DA_FIRMARE è l'XML da firmare: va firmato **senza modificarlo**.",
        "operationId": "fileFattura",
        "parameters": [{ "$ref": "#/components/parameters/Id" }],
        "responses": {
          "200": {
            "description": "File (nome nell'header Content-Disposition)",
            "content": {
              "application/xml": { "schema": { "type": "string" } },
              "application/pkcs7-mime": { "schema": { "type": "string", "format": "binary" } }
            }
          },
          "401": { "$ref": "#/components/responses/NonAutenticato" },
          "404": { "$ref": "#/components/responses/NonTrovata" }
        }
      }
    },
    "/fatture/da-firmare": {
      "get": {
        "tags": ["Firma"],
        "summary": "Fatture in attesa di firma, con l'XML da firmare",
        "description": "Fino a 100 documenti, dal più vecchio. Pensato per la firma multipla: l'app salva ogni `fileXml` con il nome `nomeFile`, il cliente li firma tutti insieme (Dike, ArubaSign, GoSign…) e l'app ricarica i .p7m con `POST /fatture/firmate`.",
        "operationId": "elencoDaFirmare",
        "responses": {
          "200": { "description": "Da firmare", "content": { "application/json": { "schema": {
            "type": "object",
            "properties": { "daFirmare": { "type": "array", "items": { "$ref": "#/components/schemas/DaFirmare" } } }
          } } } },
          "401": { "$ref": "#/components/responses/NonAutenticato" }
        }
      }
    },
    "/fatture/{id}/firmata": {
      "post": {
        "tags": ["Firma"],
        "summary": "Carica una fattura firmata e trasmettila",
        "description": "Verifichiamo che il contenuto firmato sia identico all'XML preparato, che la firma sia integra e che il certificato del firmatario sia nel periodo di validità; poi trasmettiamo allo SDI (che verifica la qualifica della firma).",
        "operationId": "caricaFirmata",
        "parameters": [{ "$ref": "#/components/parameters/Id" }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": {
          "type": "object", "required": ["fileP7m"],
          "properties": { "fileP7m": { "type": "string", "contentEncoding": "base64", "description": "File .p7m (CAdES, formato DER) in base64" } }
        } } } },
        "responses": {
          "201": { "description": "Firma verificata, fattura trasmessa (stato INVIATA)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RispostaFirmata" } } } },
          "400": { "$ref": "#/components/responses/RichiestaNonValida" },
          "401": { "$ref": "#/components/responses/NonAutenticato" },
          "404": { "description": "Documento non trovato", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RispostaFirmata" } } } },
          "409": { "description": "Il documento non è in stato DA_FIRMARE", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RispostaFirmata" } } } },
          "422": { "description": "Firma non valida, contenuto modificato o certificato scaduto: il documento resta DA_FIRMARE", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RispostaFirmata" } } } },
          "502": { "description": "Comunicazione con lo SDI interrotta (ESITO_INCERTO): non ricaricare, verifichiamo noi", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RispostaFirmata" } } } }
        }
      }
    },
    "/fatture/firmate": {
      "post": {
        "tags": ["Firma"],
        "summary": "Caricamento cumulativo delle fatture firmate",
        "description": "Fino a 100 file per richiesta. Ogni file si abbina al documento con `id` oppure con `nomeFile` (con o senza `.p7m`), viene verificato e trasmesso da solo: un file non valido non blocca gli altri.",
        "operationId": "caricaFirmate",
        "requestBody": { "required": true, "content": { "application/json": {
          "schema": {
            "type": "object", "required": ["files"],
            "properties": { "files": { "type": "array", "minItems": 1, "maxItems": 100, "items": {
              "type": "object", "required": ["fileP7m"],
              "properties": {
                "id": { "type": "integer" },
                "nomeFile": { "type": "string", "examples": ["IT02174570677_Z00A7.xml.p7m"] },
                "fileP7m": { "type": "string", "contentEncoding": "base64" }
              }
            } } }
          }
        } } },
        "responses": {
          "201": { "description": "Tutti i file trasmessi", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RispostaCumulativa" } } } },
          "207": { "description": "Esito misto: guardare `risultati` file per file", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RispostaCumulativa" } } } },
          "400": { "$ref": "#/components/responses/RichiestaNonValida" },
          "401": { "$ref": "#/components/responses/NonAutenticato" }
        }
      }
    },
    "/ricevute": {
      "get": {
        "tags": ["Fatture ricevute"],
        "summary": "Elenco delle fatture ricevute dai fornitori",
        "description": "Dalla più recente. Uso tipico: `?nonScaricate=1`, scaricare ciascuna e confermarla con `POST /ricevute/{id}/scaricata`.",
        "operationId": "elencoRicevute",
        "parameters": [
          { "name": "nonScaricate", "in": "query", "description": "1 = solo quelle non ancora confermate come scaricate", "schema": { "type": "string", "enum": ["1"] } },
          { "name": "dal", "in": "query", "description": "Solo documenti arrivati da questa data", "schema": { "type": "string", "format": "date" } },
          { "name": "partitaIva", "in": "query", "description": "Solo per questa azienda del partner", "schema": { "type": "string" } },
          { "name": "limite", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 200, "default": 50 } }
        ],
        "responses": {
          "200": { "description": "Fatture ricevute", "content": { "application/json": { "schema": {
            "type": "object", "properties": { "ricevute": { "type": "array", "items": { "$ref": "#/components/schemas/Documento" } } }
          } } } },
          "401": { "$ref": "#/components/responses/NonAutenticato" }
        }
      }
    },
    "/ricevute/{id}": {
      "get": {
        "tags": ["Fatture ricevute"],
        "summary": "Dettaglio di una fattura ricevuta con i suoi eventi",
        "operationId": "dettaglioRicevuta",
        "parameters": [{ "$ref": "#/components/parameters/Id" }],
        "responses": {
          "200": { "description": "Fattura ricevuta ed eventi", "content": { "application/json": { "schema": {
            "type": "object",
            "properties": {
              "ricevuta": { "$ref": "#/components/schemas/Documento" },
              "eventi": { "type": "array", "items": { "$ref": "#/components/schemas/Evento" } }
            }
          } } } },
          "401": { "$ref": "#/components/responses/NonAutenticato" },
          "404": { "$ref": "#/components/responses/NonTrovata" }
        }
      }
    },
    "/ricevute/{id}/file": {
      "get": {
        "tags": ["Fatture ricevute"],
        "summary": "Scarica il file di una fattura ricevuta",
        "operationId": "fileRicevuta",
        "parameters": [
          { "$ref": "#/components/parameters/Id" },
          { "name": "tipo", "in": "query", "schema": { "type": "string", "enum": ["xml", "originale", "metadati"], "default": "xml" },
            "description": "`xml` = fattura leggibile (estratta dal .p7m se firmata); `originale` = file come consegnato dallo SDI (.xml o .xml.p7m, da conservare); `metadati` = file metadati SDI della consegna" }
        ],
        "responses": {
          "200": {
            "description": "File (nome nell'header Content-Disposition)",
            "content": {
              "application/xml": { "schema": { "type": "string" } },
              "application/pkcs7-mime": { "schema": { "type": "string", "format": "binary" } }
            }
          },
          "400": { "$ref": "#/components/responses/RichiestaNonValida" },
          "401": { "$ref": "#/components/responses/NonAutenticato" },
          "404": { "$ref": "#/components/responses/NonTrovata" }
        }
      }
    },
    "/ricevute/{id}/scaricata": {
      "post": {
        "tags": ["Fatture ricevute"],
        "summary": "Conferma di aver scaricato una fattura ricevuta",
        "description": "La fattura passa in stato SCARICATA ed esce da `?nonScaricate=1`; resta comunque consultabile.",
        "operationId": "confermaScaricata",
        "parameters": [{ "$ref": "#/components/parameters/Id" }],
        "responses": {
          "200": { "description": "Confermata", "content": { "application/json": { "schema": {
            "type": "object",
            "properties": { "ok": { "type": "boolean" }, "id": { "type": "integer" }, "stato": { "type": "string", "const": "SCARICATA" } }
          } } } },
          "401": { "$ref": "#/components/responses/NonAutenticato" },
          "404": { "$ref": "#/components/responses/NonTrovata" }
        }
      }
    },
    "/eventi": {
      "get": {
        "tags": ["Eventi"],
        "summary": "Eventi in ordine, a partire da un cursore",
        "description": "Tutti gli eventi del partner (fatture emesse e ricevute), in ordine crescente di `id`. L'app salva `ultimoId` e alla chiamata successiva passa `dopo=<ultimoId>`; se `altri` è true ci sono altri eventi da leggere subito. Va usato anche da chi riceve gli avvisi automatici, come recupero periodico (es. ogni ora) di eventuali avvisi persi. Include anche gli eventi che non vengono spediti come avviso (RIFIUTATA_DA_NOI, DA_FIRMARE, FIRMATA), perché il loro esito arriva già nella risposta della chiamata.",
        "operationId": "eventi",
        "parameters": [
          { "name": "dopo", "in": "query", "description": "Restituisce gli eventi con id maggiore di questo (0 = dall'inizio)", "schema": { "type": "integer", "minimum": 0, "default": 0 } },
          { "name": "limite", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 500, "default": 100 } }
        ],
        "responses": {
          "200": { "description": "Eventi", "content": { "application/json": { "schema": {
            "type": "object",
            "properties": {
              "eventi": { "type": "array", "items": { "$ref": "#/components/schemas/Evento" } },
              "ultimoId": { "type": "integer", "description": "Da passare come `dopo` alla prossima chiamata" },
              "altri": { "type": "boolean", "description": "true = pagina piena, richiamare subito" }
            }
          } } } },
          "401": { "$ref": "#/components/responses/NonAutenticato" }
        }
      }
    }
  },
  "webhooks": {
    "avviso": {
      "post": {
        "summary": "Avviso automatico di un evento",
        "description": "Spedito all'indirizzo avvisi del partner (configurato da MORLAPPO) a ogni evento: INVIATA, CONSEGNATA, SCARTATA, NON_CONSEGNATA, ATTESTAZIONE, ESITO_PA, DECORRENZA_TERMINI, RICEVUTA.\n\n**Verifica obbligatoria della firma**: `X-NewEra-Firma` = `sha256=` + HMAC-SHA256 in esadecimale, calcolato con il segreto degli avvisi sulla stringa `<X-NewEra-Timestamp>.<corpo grezzo>`. Confrontare in tempo costante e rifiutare timestamp più vecchi di 5 minuti.\n\nRispondere 2xx entro 10 secondi (salvare e lavorare dopo). Senza 2xx si ritenta dopo 1, 5, 15, 60 minuti, 3, 6, 12 ore e poi ogni 12 ore, fino a 72 ore dall'evento. Lo stesso evento può arrivare più di una volta: deduplicare su `evento.id` (anche nell'header `X-NewEra-Evento`). Gli avvisi possono arrivare fuori ordine: lo stato valido è `fattura.stato`.",
        "parameters": [
          { "name": "X-NewEra-Evento", "in": "header", "required": true, "schema": { "type": "string" }, "description": "Id dell'evento" },
          { "name": "X-NewEra-Timestamp", "in": "header", "required": true, "schema": { "type": "string" }, "description": "Secondi Unix" },
          { "name": "X-NewEra-Firma", "in": "header", "required": true, "schema": { "type": "string", "examples": ["sha256=5f2c…"] } }
        ],
        "requestBody": { "content": { "application/json": { "schema": {
          "type": "object",
          "properties": {
            "evento": { "$ref": "#/components/schemas/Evento" },
            "fattura": { "$ref": "#/components/schemas/Documento", "description": "Il documento a cui si riferisce l'evento (emesso o ricevuto: vedi `direzione`)" }
          }
        } } } },
        "responses": { "200": { "description": "Avviso ricevuto (qualunque 2xx)" } }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "chiaveApi": {
        "type": "http",
        "scheme": "bearer",
        "description": "Header `Authorization: Bearer <chiave>`. Chiavi `nea_test_…` (SDI di test) o `nea_live_…` (produzione), consegnate da MORLAPPO una sola volta: conservarle in un archivio segreto lato server, mai nell'app del browser o del telefono. Se compromessa, si chiede la revoca e una nuova chiave."
      }
    },
    "parameters": {
      "Id": { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" }, "description": "Id del documento restituito dall'API" },
      "IdempotencyKey": { "name": "Idempotency-Key", "in": "header", "required": false, "schema": { "type": "string", "maxLength": 100 }, "description": "Valore univoco scelto dall'app per ogni fattura (es. id interno o UUID). Stessa chiave = nessun secondo invio." }
    },
    "responses": {
      "NonAutenticato": { "description": "Chiave mancante (NON_AUTENTICATO) o non valida, revocata, di un partner non attivo (CHIAVE_NON_VALIDA)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Errore" } } } },
      "TroppeRichieste": { "description": "Oltre 120 richieste al minuto per chiave (TROPPE_RICHIESTE): attendere e riprovare", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Errore" } } } },
      "RichiestaNonValida": { "description": "Richiesta malformata (JSON_NON_VALIDO, FORMA_NON_VALIDA, FILE_NON_VALIDO, ID_NON_VALIDO, STATO_NON_VALIDO, TIPO_NON_VALIDO, FILES_MANCANTI, TROPPI_FILE)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Errore" } } } },
      "DatiNonValidi": { "description": "Dati non validi (DATI_NON_VALIDI): elenco campo per campo in `dettagli`", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Errore" } } } },
      "NonTrovata": { "description": "Documento non trovato o non di questo partner (NON_TROVATA, NON_DISPONIBILE)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Errore" } } } }
    },
    "schemas": {
      "Ambiente": { "type": "string", "enum": ["TEST", "PROD"] },
      "Errore": {
        "type": "object",
        "required": ["errore"],
        "properties": { "errore": {
          "type": "object", "required": ["codice", "messaggio"],
          "properties": {
            "codice": { "type": "string", "description": "Codice stabile, da usare nella logica dell'app", "examples": ["DATI_NON_VALIDI"] },
            "messaggio": { "type": "string", "description": "Testo leggibile in italiano" },
            "dettagli": { "description": "Di solito un elenco di stringhe \"campo: problema\"", "type": "array", "items": { "type": "string" } }
          }
        } },
        "example": { "errore": { "codice": "FATTURA_NON_VALIDA", "messaggio": "La fattura non supera i controlli", "dettagli": ["cliente.provincia: cliente italiano: provincia obbligatoria", "righe.0.natura: riga con IVA 0: indicare la natura (es. N4 esente)"] } }
      },
      "AziendaInput": {
        "type": "object",
        "required": ["partitaIva", "ragioneSociale", "indirizzo", "cap", "comune", "provincia"],
        "properties": {
          "partitaIva": { "type": "string", "pattern": "^\\d{11}$", "description": "Partita IVA italiana, 11 cifre senza IT" },
          "codiceFiscale": { "type": "string", "description": "16 caratteri o 11 cifre; se assente = partita IVA" },
          "ragioneSociale": { "type": "string", "minLength": 2, "maxLength": 80 },
          "regimeFiscale": { "type": "string", "pattern": "^RF(0[1-9]|1[0-9])$", "default": "RF01", "description": "RF01 ordinario, RF19 forfettario, ecc. Con RF19 le righe vanno con aliquotaIva 0 e natura N2.2 (righe con IVA rifiutate); la dicitura di franchigia e il bollo li aggiungiamo noi." },
          "indirizzo": { "type": "string", "maxLength": 60 },
          "cap": { "type": "string", "pattern": "^\\d{5}$" },
          "comune": { "type": "string", "maxLength": 60 },
          "provincia": { "type": "string", "minLength": 2, "maxLength": 2 },
          "email": { "type": "string", "format": "email" },
          "pec": { "type": "string", "format": "email" }
        }
      },
      "Azienda": {
        "type": "object",
        "properties": {
          "partitaIva": { "type": "string" },
          "codiceFiscale": { "type": ["string", "null"] },
          "ragioneSociale": { "type": "string" },
          "regimeFiscale": { "type": "string" },
          "indirizzo": { "type": ["string", "null"] },
          "cap": { "type": ["string", "null"] },
          "comune": { "type": ["string", "null"] },
          "provincia": { "type": ["string", "null"] },
          "attiva": { "type": "boolean" },
          "attivataIl": { "type": "string", "format": "date-time" }
        }
      },
      "RispostaAzienda": { "type": "object", "properties": { "azienda": { "$ref": "#/components/schemas/Azienda" } } },
      "RichiestaInvio": {
        "type": "object",
        "description": "Uno solo tra `fattura`, `fileXml`, `fileP7m`",
        "properties": {
          "fattura": { "$ref": "#/components/schemas/FatturaJson" },
          "fileXml": { "type": "string", "contentEncoding": "base64", "description": "XML FatturaPA 1.2.2 non firmato" },
          "fileP7m": { "type": "string", "contentEncoding": "base64", "description": "XML firmato CAdES (.p7m)" },
          "firma": { "type": "boolean", "default": false, "description": "Solo con `fattura` o `fileXml`: true = la fattura resta DA_FIRMARE anche se non è per la PA" }
        }
      },
      "FatturaJson": {
        "type": "object",
        "required": ["cedentePartitaIva", "numero", "data", "cliente", "righe"],
        "properties": {
          "cedentePartitaIva": { "type": "string", "description": "Partita IVA dell'azienda che emette: deve essere attivata (POST /aziende)" },
          "tipoDocumento": { "type": "string", "enum": ["TD01", "TD02", "TD03", "TD04", "TD05", "TD06", "TD24", "TD25"], "default": "TD01", "description": "TD01 fattura, TD04 nota di credito, TD05 nota di debito, TD24 fattura differita…" },
          "numero": { "type": "string", "maxLength": 20, "description": "Numero assegnato dall'app (la numerazione è dell'azienda che emette)" },
          "data": { "type": "string", "format": "date", "description": "Non nel futuro" },
          "causale": { "type": "string", "maxLength": 2000 },
          "cliente": { "$ref": "#/components/schemas/Cliente" },
          "righe": { "type": "array", "minItems": 1, "maxItems": 1000, "items": { "$ref": "#/components/schemas/Riga" } },
          "bollo": { "oneOf": [{ "type": "string", "const": "auto" }, { "type": "boolean" }], "default": "auto", "description": "\"auto\" = bollo virtuale di 2 € se le righe esenti/escluse (N1, N2.1, N2.2, N4) superano 77,47 €; true/false per forzare. Il bollo indicato va poi versato dall'azienda (F24 trimestrale)" },
          "ritenuta": {
            "type": "object", "required": ["tipo", "aliquota", "causalePagamento"],
            "description": "Ritenuta d'acconto, applicata alle righe con \"ritenuta\": true",
            "properties": {
              "tipo": { "type": "string", "enum": ["RT01", "RT02", "RT03", "RT04", "RT05", "RT06"] },
              "aliquota": { "type": "number", "exclusiveMinimum": 0, "maximum": 100 },
              "causalePagamento": { "type": "string", "maxLength": 2, "examples": ["A"] }
            }
          },
          "pagamento": {
            "type": "object", "required": ["modalita"],
            "properties": {
              "modalita": { "type": "string", "pattern": "^MP(0[1-9]|1\\d|2[0-3])$", "description": "MP01 contanti, MP05 bonifico, MP08 carta di pagamento, MP23 PagoPA…" },
              "scadenza": { "type": "string", "format": "date", "description": "Se assente = data della fattura" },
              "iban": { "type": "string", "maxLength": 34 }
            }
          },
          "ordineAcquisto": {
            "type": "object", "required": ["idDocumento"],
            "description": "Riferimenti d'ordine (per la PA: CIG e CUP)",
            "properties": {
              "idDocumento": { "type": "string", "maxLength": 20 },
              "data": { "type": "string", "format": "date" },
              "cig": { "type": "string", "maxLength": 15 },
              "cup": { "type": "string", "maxLength": 15 }
            }
          },
          "fattureCollegate": {
            "type": "array", "maxItems": 50, "description": "Obbligatorio per le note di credito (TD04): la fattura che si storna",
            "items": { "type": "object", "required": ["numero", "data"], "properties": { "numero": { "type": "string" }, "data": { "type": "string", "format": "date" } } }
          }
        }
      },
      "Cliente": {
        "type": "object",
        "required": ["indirizzo", "comune"],
        "description": "Denominazione OPPURE nome e cognome; partita IVA OPPURE codice fiscale; provincia obbligatoria per i clienti italiani",
        "properties": {
          "denominazione": { "type": "string", "maxLength": 80 },
          "nome": { "type": "string", "maxLength": 60 },
          "cognome": { "type": "string", "maxLength": 60 },
          "partitaIva": { "type": "string", "maxLength": 28 },
          "codiceFiscale": { "type": "string", "maxLength": 16 },
          "nazione": { "type": "string", "minLength": 2, "maxLength": 2, "default": "IT" },
          "indirizzo": { "type": "string", "maxLength": 60 },
          "cap": { "type": "string", "maxLength": 10, "default": "00000" },
          "comune": { "type": "string", "maxLength": 60 },
          "provincia": { "type": "string", "maxLength": 2 },
          "codiceDestinatario": { "type": "string", "pattern": "^([A-Z0-9]{6}|[A-Z0-9]{7})$", "description": "7 caratteri per aziende e professionisti, 6 per la PA (codice univoco ufficio IPA). Assente = privato o recapito via PEC (0000000)" },
          "pec": { "type": "string", "format": "email" }
        }
      },
      "Riga": {
        "type": "object",
        "required": ["descrizione", "prezzoUnitario", "aliquotaIva"],
        "properties": {
          "descrizione": { "type": "string", "maxLength": 1000 },
          "quantita": { "type": "number", "exclusiveMinimum": 0, "default": 1 },
          "unitaMisura": { "type": "string", "maxLength": 10 },
          "prezzoUnitario": { "type": "number", "description": "Al netto dell'IVA" },
          "scontoPercentuale": { "type": "number", "minimum": 0, "maximum": 100 },
          "aliquotaIva": { "type": "number", "minimum": 0, "maximum": 100, "examples": [22, 10, 4, 0] },
          "natura": { "type": "string", "enum": ["N1", "N2.1", "N2.2", "N3.1", "N3.2", "N3.3", "N3.4", "N3.5", "N3.6", "N4", "N5", "N6.1", "N6.2", "N6.3", "N6.4", "N6.5", "N6.6", "N6.7", "N6.8", "N6.9", "N7"], "description": "Obbligatoria se aliquotaIva = 0, vietata altrimenti" },
          "riferimentoNormativo": { "type": "string", "maxLength": 100, "description": "Norma di esenzione/esclusione; se assente usiamo un testo standard per N1, N2.x, N4. Per le aziende in regime forfettario (RF19) il testo standard di N2.2 è la dicitura di franchigia: \"Operazione in franchigia da IVA ai sensi dell'art. 1, c. 54-89, L. 190/2014 (forfettario)\"" },
          "ritenuta": { "type": "boolean", "description": "Riga soggetta a ritenuta d'acconto" }
        }
      },
      "Totali": {
        "type": "object",
        "description": "Calcolati da noi (solo per gli invii JSON), arrotondati al centesimo per riga e per riepilogo IVA",
        "properties": {
          "imponibile": { "type": "number" },
          "imposta": { "type": "number" },
          "totaleDocumento": { "type": "number" },
          "bollo": { "type": "number" },
          "ritenuta": { "type": "number" },
          "nettoAPagare": { "type": "number" }
        }
      },
      "StatoEmessa": {
        "type": "string",
        "enum": ["IN_LAVORAZIONE", "RIFIUTATA_DA_NOI", "DA_FIRMARE", "ESITO_INCERTO", "INVIATA", "SCARTATA", "CONSEGNATA", "NON_CONSEGNATA", "IMPOSSIBILE_RECAPITO", "ACCETTATA_PA", "RIFIUTATA_PA", "DECORRENZA_TERMINI"],
        "description": "Vedi la tabella degli stati nella guida"
      },
      "StatoRicevuta": { "type": "string", "enum": ["RICEVUTA", "SCARICATA"] },
      "Documento": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "direzione": { "type": "string", "enum": ["EMESSA", "RICEVUTA"] },
          "ambiente": { "$ref": "#/components/schemas/Ambiente" },
          "stato": { "oneOf": [{ "$ref": "#/components/schemas/StatoEmessa" }, { "$ref": "#/components/schemas/StatoRicevuta" }] },
          "identificativoSdi": { "type": ["string", "null"], "description": "IdSdI assegnato dallo SDI alla presa in carico" },
          "nomeFile": { "type": "string", "examples": ["IT02174570677_Z00A7.xml"] },
          "cedentePartitaIva": { "type": ["string", "null"], "description": "Solo EMESSA: l'azienda che emette" },
          "destinatarioPartitaIva": { "type": ["string", "null"], "description": "Solo RICEVUTA: l'azienda del partner che riceve" },
          "formato": { "type": ["string", "null"], "enum": ["FPR12", "FPA12", null] },
          "tipoDocumento": { "type": ["string", "null"] },
          "numero": { "type": ["string", "null"] },
          "data": { "type": ["string", "null"], "format": "date" },
          "codiceDestinatario": { "type": ["string", "null"] },
          "controparte": {
            "type": "object", "description": "EMESSA = il cliente; RICEVUTA = il fornitore",
            "properties": {
              "denominazione": { "type": ["string", "null"] },
              "partitaIva": { "type": ["string", "null"] },
              "codiceFiscale": { "type": ["string", "null"] }
            }
          },
          "importoTotale": { "type": ["number", "null"] },
          "errori": { "type": ["array", "null"], "items": { "type": "string" }, "description": "Motivi di RIFIUTATA_DA_NOI / ESITO_INCERTO (per gli scarti SDI vedi l'evento SCARTATA)" },
          "chiaveIdempotenza": { "type": ["string", "null"] },
          "scaricataIl": { "type": ["string", "null"], "format": "date-time" },
          "creatoIl": { "type": "string", "format": "date-time" },
          "aggiornatoIl": { "type": "string", "format": "date-time" }
        }
      },
      "Evento": {
        "type": "object",
        "properties": {
          "id": { "type": "integer", "description": "Crescente: è il cursore di GET /eventi e la chiave di deduplica degli avvisi" },
          "documentoId": { "type": "integer" },
          "tipo": { "type": "string", "enum": ["RIFIUTATA_DA_NOI", "DA_FIRMARE", "FIRMATA", "INVIATA", "SCARTATA", "CONSEGNATA", "NON_CONSEGNATA", "ATTESTAZIONE", "ESITO_PA", "DECORRENZA_TERMINI", "RICEVUTA"] },
          "identificativoSdi": { "type": ["string", "null"] },
          "nomeFileNotifica": { "type": ["string", "null"], "description": "Nome del file di notifica SDI (RC, NS, MC, AT, NE, DT)" },
          "dettaglio": {
            "type": ["object", "null"],
            "description": "Dipende dal tipo. SCARTATA: `errori` [{codice, descrizione, suggerimento}] dello SDI. ESITO_PA: `esitoPA` (EC01 accettata / EC02 rifiutata) e `motivoPA`. CONSEGNATA: `dataOraConsegna`. RICEVUTA: `fornitore`, `numero`, `data`. RIFIUTATA_DA_NOI: `errori`. FIRMATA: `firmatario`. Le notifiche SDI portano anche `statoDocumento`.",
            "additionalProperties": true
          },
          "creatoIl": { "type": "string", "format": "date-time" }
        }
      },
      "RispostaInvio": {
        "type": "object",
        "properties": {
          "fattura": { "oneOf": [{ "$ref": "#/components/schemas/Documento" }, { "type": "null" }] },
          "totali": { "$ref": "#/components/schemas/Totali" },
          "avviso": { "type": "string", "description": "Presente se la Idempotency-Key era già stata usata" },
          "errori": { "type": "array", "items": { "type": "string" } }
        }
      },
      "RispostaFirmata": {
        "type": "object",
        "properties": {
          "fattura": { "oneOf": [{ "$ref": "#/components/schemas/Documento" }, { "type": "null" }] },
          "firmatario": { "type": ["string", "null"], "description": "Nome del titolare del certificato (CN)" },
          "errori": { "type": "array", "items": { "type": "string" } }
        }
      },
      "DaFirmare": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "nomeFile": { "type": "string", "description": "Nome con cui salvare l'XML da firmare" },
          "nomeFileFirmato": { "type": "string", "description": "Nome atteso del file firmato" },
          "numero": { "type": ["string", "null"] },
          "data": { "type": ["string", "null"], "format": "date" },
          "cliente": { "type": ["string", "null"] },
          "importoTotale": { "type": ["number", "null"] },
          "fileXml": { "type": "string", "contentEncoding": "base64" }
        }
      },
      "RispostaCumulativa": {
        "type": "object",
        "properties": {
          "trasmesse": { "type": "integer" },
          "errori": { "type": "integer" },
          "risultati": { "type": "array", "items": {
            "type": "object",
            "properties": {
              "indice": { "type": "integer", "description": "Posizione nell'elenco inviato" },
              "id": { "type": "integer" },
              "nomeFile": { "type": ["string", "null"] },
              "esito": { "type": "string", "enum": ["TRASMESSA", "ERRORE"] },
              "firmatario": { "type": ["string", "null"] },
              "errori": { "type": "array", "items": { "type": "string" } }
            }
          } }
        }
      }
    }
  }
}
