{
  "openapi": "3.1.0",
  "info": {
    "title": "e-Factura Web — API pentru integrări",
    "version": "1.0.0",
    "description": "Emiterea facturilor și depunerea lor în SPV din aplicații externe (WooCommerce, ERP, scripturi proprii). Un singur apel poate crea clientul, emite factura, genera XML-ul UBL 2.1 CIUS-RO, îl valida la ANAF și îl încărca în SPV.\n\nToate cererile și răspunsurile folosesc UTF-8. Textul care conține caracterul de înlocuire U+FFFD este respins cu 400: e semnul că a fost trimis în alt set de caractere, iar diacriticele sunt deja pierdute.",
    "contact": { "name": "Suport e-Factura Web", "email": "contact@e-factura.app" }
  },
  "servers": [{ "url": "https://facturare.completez.ro/api/v1" }],
  "security": [{ "cheieApi": [] }],
  "tags": [
    { "name": "Diagnostic", "description": "Verificarea cheii, starea firmei și rulajul anual" },
    { "name": "Facturi", "description": "Emitere, depunere, stornare, descărcare, răspunsul ANAF" },
    { "name": "Proforme", "description": "Documente care preced plata; se convertesc în facturi" },
    { "name": "Contracte", "description": "Contracte, acordul GDPR și facturile emise în baza lor" },
    { "name": "SPV", "description": "Mesajele firmei din SPV: facturi primite și trimise, erori, mesaje de la cumpărători" }
  ],
  "paths": {
    "/ping": {
      "get": {
        "tags": ["Diagnostic"],
        "summary": "Verifică cheia și mediul",
        "description": "Nu are efecte. Folosit pentru a confirma că cheia e validă și pentru a afla în ce mediu va emite.",
        "responses": {
          "200": {
            "description": "Cheia este validă",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": { "type": "boolean" },
                    "versiune": { "type": "string", "examples": ["v1"] },
                    "cheie": {
                      "type": "object",
                      "properties": {
                        "nume": { "type": "string" },
                        "prefix": { "type": "string", "examples": ["efk_A1b2C3d4"] },
                        "drepturi": { "type": "array", "items": { "type": "string", "enum": ["read", "write"] } },
                        "cheieDeTest": { "type": "boolean" }
                      }
                    },
                    "firma": {
                      "type": ["object", "null"],
                      "properties": {
                        "id": { "type": "string" },
                        "denumire": { "type": "string" },
                        "cui": { "type": "string" },
                        "modTest": { "type": "boolean" }
                      }
                    },
                    "mediu": { "type": "string", "enum": ["test", "productie"] },
                    "server": { "type": "string", "format": "date-time" }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Neautorizat" },
          "403": { "$ref": "#/components/responses/AdresaNepermisa" }
        }
      }
    },
    "/company": {
      "get": {
        "tags": ["Diagnostic"],
        "summary": "Datele firmei, autorizarea ANAF și cota lunară",
        "description": "Merită interogat înainte de prima emitere a zilei: dacă `anaf.autorizat` este `false`, depunerile vor eșua până când firma e reautorizată din aplicație.",
        "responses": {
          "200": {
            "description": "Starea firmei",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/Firma" } }
            }
          },
          "401": { "$ref": "#/components/responses/Neautorizat" },
          "403": { "$ref": "#/components/responses/AdresaNepermisa" },
          "404": { "$ref": "#/components/responses/NegasitFirma" }
        }
      }
    },
    "/company/rulaj": {
      "get": {
        "tags": ["Diagnostic"],
        "summary": "Rulajul anual al firmei, în lei",
        "description": "Cifra de afaceri a firmei într-un an calendaristic, pentru pragurile fiscale (plafonul de TVA, cel de microîntreprindere). Intră facturile neanulate cu `dataEmitere` în anul cerut, din mediul cheii: o cheie de producție numără doar facturile reale — chiar dacă firma e comutată pe test —, iar una de test doar facturile de test. Stornourile intră cu minus. O factură în valută intră la cursul ei (`cursValutar`), fiecare sumă rotunjită la bani; una în valută fără curs nu intră în sume și se numără în `faraCurs`.",
        "parameters": [
          {
            "name": "an",
            "in": "query",
            "required": false,
            "description": "Anul, între 2000 și 2100. Lipsă = anul curent, la ora României.",
            "schema": { "type": "integer", "minimum": 2000, "maximum": 2100 }
          }
        ],
        "responses": {
          "200": {
            "description": "Rulajul anului, cu toate cele 12 luni",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Rulaj" } } }
          },
          "400": {
            "description": "`an` nenumeric sau în afara intervalului 2000–2100.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Eroare" } } }
          },
          "401": { "$ref": "#/components/responses/Neautorizat" },
          "403": { "$ref": "#/components/responses/AdresaNepermisa" },
          "404": { "$ref": "#/components/responses/NegasitFirma" },
          "429": { "$ref": "#/components/responses/PreaMulteCereri" }
        }
      }
    },
    "/invoices": {
      "post": {
        "tags": ["Facturi"],
        "summary": "Creează factura și, implicit, o depune în SPV",
        "description": "Necesită dreptul `write`.\n\nClientul e identificat după `externalId`, apoi după CUI, apoi după denumire; dacă nu există, e creat. Când se trimite doar CUI-ul unei firme, restul datelor se completează din registrul ANAF.\n\nNumărul facturii e alocat de server din seria firmei, iar totalurile sunt recalculate din linii — valorile trimise pentru ele sunt ignorate.\n\nFactura se creează chiar dacă depunerea eșuează: primiți tot 201, cu motivul în `depunere`. Ștergerea ei ar lăsa o gaură în numerotare, așa că reluarea se face cu `POST /invoices/{id}/submit`.\n\nClientul găsit după `externalId` primește datele din cerere (nume, adresă, țară, cod poștal, e-mail, coduri): adresa, e-mailul și codurile lipsă din cerere se golesc. O cerere doar cu `externalId` nu-l schimbă; clientul găsit după CUI sau denumire rămâne neatins.\n\nLa o firmă neplătitoare de TVA, fiecare linie e în categoria O, fără TVA, oricare ar fi `cotaTva`.\n\nÎn valută, factura primește `cursValutar` (cel trimis, altfel cursul BNR valabil la data facturii). Dacă BNR nu răspunde, factura se creează cu `depunere.status: \"fara_curs\"`, fără XML și fără depunere; dacă BNR răspunde, dar n-are cursul (monedă pe care n-o publică, dată fără curs), nu se creează nimic: 422.\n\nFactură în lei, plătită în valută: cu `moneda: \"RON\"` și `monedaPlata` (ex. `USD`), prețurile liniilor sunt în moneda plății și se trec în lei la cursul BNR valabil la data facturii, sau la `cursValutar`. Fiecare linie se rotunjește la bani, iar totalul e suma liniilor. Factura ține `monedaPlata`, `cursPlata`, `dataCursPlata`, `sursaCursPlata` și `totalPlata`, iar XML-ul și PDF-ul au mențiunea „Plătit 15,00 USD; curs BNR din 30.10.2026: 1 USD = 4,7698 lei.”. Fără curs nu se creează nimic: 503 dacă BNR nu răspunde (se poate reîncerca), 422 dacă BNR n-are cursul (reîncercarea n-ar ajuta).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/CerereFactura" },
              "examples": {
                "comandaMagazin": {
                  "summary": "Comandă dintr-un magazin online",
                  "value": {
                    "externalId": "wc_order_1234",
                    "client": {
                      "cui": "RO12345678",
                      "denumire": "SC CLIENT SRL",
                      "adresa": "Str. Exemplu nr. 10",
                      "localitate": "Cluj-Napoca",
                      "judet": "Cluj"
                    },
                    "linii": [
                      { "denumire": "Abonament lunar", "cantitate": 1, "pretUnitar": 199.99, "cotaTva": "21" },
                      { "denumire": "Transport", "cantitate": 1, "pretUnitar": 25, "cotaTva": "21" }
                    ]
                  }
                },
                "serviciuScutit": {
                  "summary": "Serviciu scutit de TVA, client persoană fizică",
                  "value": {
                    "externalId": "pay_9f2c",
                    "seria": "RAI",
                    "client": {
                      "externalId": "pacient_4471",
                      "denumire": "Popescu Maria",
                      "adresa": "Str. Florilor nr. 3",
                      "localitate": "Bistrița",
                      "judet": "Bistrița-Năsăud"
                    },
                    "linii": [
                      { "denumire": "Ședință de evaluare inițială", "cantitate": 1, "pretUnitar": 700, "cotaTva": "E" }
                    ],
                    "scutireTva": {
                      "cod": "VATEX-EU-132-1C",
                      "motiv": "Scutit conform art. 292 alin. (1) lit. a) Cod fiscal"
                    }
                  }
                },
                "abonamentUsd": {
                  "summary": "Abonament plătit cu cardul, în USD, de o firmă din Germania (fără depunere în SPV)",
                  "value": {
                    "externalId": "in_1QabcD",
                    "moneda": "usd",
                    "modPlata": "card",
                    "depune": false,
                    "client": {
                      "externalId": "cus_R2xyz",
                      "denumire": "Beispiel Software GmbH",
                      "cui": "DE123456789",
                      "tara": "DE",
                      "adresa": "Friedrichstraße 100",
                      "localitate": "Berlin",
                      "codPostal": "10117"
                    },
                    "linii": [
                      { "denumire": "Abonament Business - lunar", "cantitate": 1, "pretUnitar": 49.9, "um": "luna", "umUbl": "MON" }
                    ]
                  }
                },
                "abonamentLeiDinUsd": {
                  "summary": "Abonament plătit în USD de o firmă din România, facturat în lei",
                  "value": {
                    "externalId": "in_1QefgH",
                    "moneda": "RON",
                    "monedaPlata": "USD",
                    "modPlata": "card",
                    "depune": true,
                    "client": {
                      "externalId": "cus_R3abc",
                      "denumire": "SC CLIENT SRL",
                      "cui": "RO12345678",
                      "tara": "RO",
                      "adresa": "Str. Lăpușneanu 12",
                      "localitate": "Iași",
                      "judet": "Iași",
                      "codPostal": "700057"
                    },
                    "linii": [
                      { "denumire": "Abonament Pro - lunar", "cantitate": 1, "pretUnitar": 29, "um": "luna", "umUbl": "MON" }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Factura a fost creată. `depunere` spune dacă a și ajuns la ANAF.",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/RaspunsCreare" } }
            }
          },
          "200": {
            "description": "Există deja o factură cu acest `externalId` în mediul curent. Nu s-a creat alta.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "duplicat": { "type": "boolean", "const": true },
                    "factura": { "$ref": "#/components/schemas/Factura" }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/DateInvalide" },
          "401": { "$ref": "#/components/responses/Neautorizat" },
          "402": { "$ref": "#/components/responses/LimitaPlan" },
          "403": { "$ref": "#/components/responses/DoarCitire" },
          "409": { "$ref": "#/components/responses/Conflict" },
          "429": { "$ref": "#/components/responses/PreaMulteCereri" },
          "422": {
            "description": "BNR n-are cursul cerut — monedă pe care n-o publică (ex. `monedaPlata: \"XYZ\"`) sau dată fără curs —, deci factura nu s-a creat. O reîncercare ar primi același răspuns: corectați moneda sau data, ori trimiteți `cursValutar`.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Eroare" } } }
          },
          "503": {
            "description": "Cu `monedaPlata`: BNR n-a putut fi întrebat acum, deci factura nu s-a creat (sumele în lei depind de curs). Reîncercați mai târziu sau trimiteți `cursValutar`.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Eroare" } } }
          }
        }
      },
      "get": {
        "tags": ["Facturi"],
        "summary": "Listă paginată de facturi",
        "description": "O cheie de test vede exclusiv facturi de test.",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "description": "Filtrează după starea depunerii.",
            "schema": { "$ref": "#/components/schemas/StareDepunere" }
          },
          { "name": "externalId", "in": "query", "schema": { "type": "string" } },
          {
            "name": "from",
            "in": "query",
            "description": "Data emiterii, de la (inclusiv).",
            "schema": { "type": "string", "format": "date" }
          },
          {
            "name": "to",
            "in": "query",
            "description": "Data emiterii, până la (inclusiv).",
            "schema": { "type": "string", "format": "date" }
          },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 50, "minimum": 1, "maximum": 200 } },
          { "name": "offset", "in": "query", "schema": { "type": "integer", "default": 0, "minimum": 0 } }
        ],
        "responses": {
          "200": {
            "description": "Lista facturilor",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "total": { "type": "integer" },
                    "limit": { "type": "integer" },
                    "offset": { "type": "integer" },
                    "facturi": { "type": "array", "items": { "$ref": "#/components/schemas/Factura" } }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Neautorizat" },
          "403": { "$ref": "#/components/responses/AdresaNepermisa" }
        }
      }
    },
    "/invoices/{id}": {
      "parameters": [{ "$ref": "#/components/parameters/IdFactura" }],
      "get": {
        "tags": ["Facturi"],
        "summary": "Returnează o factură",
        "parameters": [
          {
            "name": "refresh",
            "in": "query",
            "description": "Cu `1`, `true` sau `da`, starea se cere pe loc de la ANAF, iar rezultatul apare în câmpul `anaf`. Alternativa la webhook-uri.",
            "schema": { "type": "string", "enum": ["1", "true", "da"] }
          }
        ],
        "responses": {
          "200": {
            "description": "Factura",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/Factura" },
                    {
                      "type": "object",
                      "properties": {
                        "anaf": {
                          "type": "object",
                          "description": "Prezent doar când s-a cerut `refresh`.",
                          "properties": {
                            "stare": { "type": "string", "examples": ["ok", "nok", "in prelucrare"] },
                            "downloadId": { "type": ["string", "null"] },
                            "eroare": { "type": "string" }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Neautorizat" },
          "403": { "$ref": "#/components/responses/AdresaNepermisa" },
          "404": { "$ref": "#/components/responses/NegasitFactura" }
        }
      }
    },
    "/invoices/{id}/submit": {
      "parameters": [{ "$ref": "#/components/parameters/IdFactura" }],
      "post": {
        "tags": ["Facturi"],
        "summary": "Depune o factură care nu a fost încă trimisă",
        "description": "Necesită dreptul `write`. Folosit după o eroare de depunere sau după o creare cu `depune: false`. Dacă XML-ul lipsește (generarea a eșuat la creare, iar datele au fost corectate între timp, sau cursul BNR nu era disponibil), e generat acum; la fel XML-ul unei facturi în valută fără curs.",
        "responses": {
          "200": {
            "description": "Rezultatul depunerii",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "reusita": { "type": "boolean" },
                    "status": { "$ref": "#/components/schemas/StareDepunere" },
                    "indexIncarcare": { "type": ["string", "null"] },
                    "mesaj": { "type": "string" },
                    "validare": { "$ref": "#/components/schemas/RezultatValidare" },
                    "factura": { "$ref": "#/components/schemas/Factura" }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/DateInvalide" },
          "401": { "$ref": "#/components/responses/Neautorizat" },
          "403": { "$ref": "#/components/responses/DoarCitire" },
          "404": { "$ref": "#/components/responses/NegasitFactura" },
          "409": {
            "description": "Factura a fost deja depusă sau este anulată.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": { "type": "string" },
                    "indexIncarcare": { "type": ["string", "null"] },
                    "status": { "type": ["string", "null"] }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/invoices/{id}/storno": {
      "parameters": [{ "$ref": "#/components/parameters/IdFactura" }],
      "post": {
        "tags": ["Facturi"],
        "summary": "Emite factura de stornare și o depune",
        "description": "Necesită dreptul `write`. Operațiunea potrivită pentru o rambursare: o factură ajunsă la ANAF nu mai poate fi ștearsă sau modificată. Storno-ul preia valorile negate, rămâne în mediul documentului original și îi moștenește temeiul de scutire, moneda și cursul (în lei e exact opusul facturii), inclusiv plata în valută a unei facturi în lei: aceleași sume în lei cu minus, aceeași mențiune „Plătit …”, `totalPlata` negativ. XML-ul lui trimite la factura anulată (BillingReference: numărul și data). Idempotența se verifică înaintea stării: reîncercarea cu același `externalId` întoarce 200 cu stornoul deja făcut, chiar dacă factura apare acum ca stornată.",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "externalId": {
                    "type": "string",
                    "maxLength": 120,
                    "description": "Recomandat, ex. `wc_refund_1234`. Asigură idempotența stornării.",
                    "examples": ["wc_refund_1234"]
                  },
                  "depune": { "type": "boolean", "default": true }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Storno creat",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/RaspunsCreare" },
                    {
                      "type": "object",
                      "properties": {
                        "stornatDin": { "type": "string", "description": "Id-ul facturii anulate." }
                      }
                    }
                  ]
                }
              }
            }
          },
          "200": {
            "description": "Există deja un storno al acestei facturi cu acest `externalId` (reîncercare). Nu s-a creat altul.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "duplicat": { "type": "boolean", "const": true },
                    "factura": { "$ref": "#/components/schemas/Factura" },
                    "stornatDin": { "type": "string", "description": "Id-ul facturii anulate." }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Neautorizat" },
          "403": { "$ref": "#/components/responses/DoarCitire" },
          "404": { "$ref": "#/components/responses/NegasitFactura" },
          "409": {
            "description": "Factura e deja stornată (de alt storno), sau `externalId` aparține altui document.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Eroare" } } }
          }
        }
      }
    },
    "/invoices/{id}/xml": {
      "parameters": [{ "$ref": "#/components/parameters/IdFactura" }],
      "get": {
        "tags": ["Facturi"],
        "summary": "XML-ul UBL 2.1 depus la ANAF",
        "description": "Dacă factura nu are încă XML (de pildă, fiindcă BNR nu a răspuns la creare), îl generează acum, fără să-l depună.",
        "responses": {
          "200": {
            "description": "Fișierul XML",
            "content": { "application/xml": { "schema": { "type": "string" } } }
          },
          "400": { "$ref": "#/components/responses/DateInvalide" },
          "401": { "$ref": "#/components/responses/Neautorizat" },
          "403": { "$ref": "#/components/responses/AdresaNepermisa" },
          "404": {
            "description": "Factura nu există sau XML-ul nu a fost generat.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Eroare" } } }
          },
          "409": {
            "description": "XML-ul lipsea și nu s-a putut genera nici acum (de pildă, cursul BNR tot nu e disponibil).",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Eroare" } } }
          }
        }
      }
    },
    "/invoices/{id}/pdf": {
      "parameters": [{ "$ref": "#/components/parameters/IdFactura" }],
      "get": {
        "tags": ["Facturi"],
        "summary": "PDF-ul facturii",
        "description": "Fără parametri: randat de serviciul oficial ANAF din același XML care se depune, deci reprezintă exact documentul transmis (cere ca XML-ul să existe). Cu `?model=modern`: factura desenată de aplicație, în modelul implicit din interfață — furnizorul cu conturile bancare, clientul, liniile, sumarul de TVA și totalul —, făcută pe loc din datele facturii, deci disponibilă și înaintea unei depuneri reușite; e documentul potrivit de dat clientului. O factură emisă în mediul de test poartă filigranul „TEST”, iar una anulată, „ANULATĂ”. La o firmă neplătitoare scrie „Neplătitor de TVA”, iar o factură în valută are rândul cu cursul și totalul în lei. Cere autentificare, deci nu poate fi folosit ca link public: proxy-ați-l prin aplicația dumneavoastră.",
        "parameters": [
          {
            "name": "model",
            "in": "query",
            "required": false,
            "description": "Modelul de factură al aplicației. Absent = redarea ANAF a XML-ului.",
            "schema": { "type": "string", "enum": ["modern"] }
          }
        ],
        "responses": {
          "200": {
            "description": "Fișierul PDF",
            "content": { "application/pdf": { "schema": { "type": "string", "format": "binary" } } }
          },
          "401": { "$ref": "#/components/responses/Neautorizat" },
          "403": { "$ref": "#/components/responses/AdresaNepermisa" },
          "404": { "$ref": "#/components/responses/NegasitFactura" }
        }
      }
    },
    "/invoices/{id}/raspuns": {
      "parameters": [{ "$ref": "#/components/parameters/IdFactura" }],
      "get": {
        "tags": ["Facturi"],
        "summary": "Răspunsul ANAF al facturii depuse (ZIP)",
        "description": "Arhiva pusă de ANAF în SPV pentru factura depusă: factura semnată de ANAF când starea e `ok`, erorile când e `nok`. Prima cerere întreabă ANAF starea (ca `?refresh=1`, deci `depunere.status` se actualizează), descarcă arhiva și o păstrează pe server; următoarele o servesc de acolo, fără ANAF — care permite doar 10 descărcări pe zi pentru un mesaj.",
        "responses": {
          "200": {
            "description": "Arhiva ZIP, ca atașament `<SERIE>_<NUMAR>_raspuns_ANAF.zip`",
            "content": { "application/zip": { "schema": { "type": "string", "format": "binary" } } }
          },
          "400": { "$ref": "#/components/responses/DateInvalide" },
          "401": { "$ref": "#/components/responses/Neautorizat" },
          "403": { "$ref": "#/components/responses/AdresaNepermisa" },
          "404": { "$ref": "#/components/responses/NegasitFactura" },
          "409": {
            "description": "Răspunsul nu există încă: factura nu a fost depusă („Factura nu a fost depusă la ANAF”), ANAF o prelucrează („ANAF încă prelucrează factura”) sau nu a dat un răspuns descărcabil (de pildă „XML cu erori nepreluat de sistem”). Tot 409 când firma nu e autorizată la ANAF.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Eroare" } } }
          },
          "429": { "$ref": "#/components/responses/PreaMulteCereri" },
          "502": { "$ref": "#/components/responses/EroareAnaf" }
        }
      }
    },
    "/proformas": {
      "post": {
        "tags": ["Proforme"],
        "summary": "Creează o proformă",
        "description": "Necesită dreptul `write`. Proforma nu e document fiscal: nu se depune la ANAF și nu are XML. Clientul se rezolvă la fel ca la facturi, iar adresa lui e verificată de pe acum, fiindcă documentul ajunge oricum factură.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": { "schema": { "$ref": "#/components/schemas/CerereProforma" } }
          }
        },
        "responses": {
          "201": {
            "description": "Proforma a fost creată",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "duplicat": { "type": "boolean", "const": false },
                    "proforma": { "$ref": "#/components/schemas/Proforma" }
                  }
                }
              }
            }
          },
          "200": {
            "description": "Există deja o proformă cu acest `externalId` în mediul curent.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "duplicat": { "type": "boolean", "const": true },
                    "proforma": { "$ref": "#/components/schemas/Proforma" }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/DateInvalide" },
          "401": { "$ref": "#/components/responses/Neautorizat" },
          "403": { "$ref": "#/components/responses/DoarCitire" },
          "409": {
            "description": "Firma nu are nicio serie de proforme definită.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Eroare" } } }
          },
          "429": { "$ref": "#/components/responses/PreaMulteCereri" }
        }
      },
      "get": {
        "tags": ["Proforme"],
        "summary": "Listă paginată de proforme",
        "parameters": [
          { "name": "externalId", "in": "query", "schema": { "type": "string" } },
          { "name": "anulata", "in": "query", "schema": { "type": "string", "enum": ["true", "false"] } },
          {
            "name": "facturata",
            "in": "query",
            "description": "Filtrează după existența facturii generate din proformă.",
            "schema": { "type": "string", "enum": ["true", "false"] }
          },
          { "name": "from", "in": "query", "schema": { "type": "string", "format": "date" } },
          { "name": "to", "in": "query", "schema": { "type": "string", "format": "date" } },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 50, "minimum": 1, "maximum": 200 } },
          { "name": "offset", "in": "query", "schema": { "type": "integer", "default": 0, "minimum": 0 } }
        ],
        "responses": {
          "200": {
            "description": "Lista proformelor",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "total": { "type": "integer" },
                    "limit": { "type": "integer" },
                    "offset": { "type": "integer" },
                    "proforme": { "type": "array", "items": { "$ref": "#/components/schemas/Proforma" } }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Neautorizat" },
          "403": { "$ref": "#/components/responses/AdresaNepermisa" }
        }
      }
    },
    "/proformas/{id}": {
      "parameters": [{ "$ref": "#/components/parameters/IdProforma" }],
      "get": {
        "tags": ["Proforme"],
        "summary": "Returnează o proformă",
        "responses": {
          "200": {
            "description": "Proforma",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Proforma" } } }
          },
          "401": { "$ref": "#/components/responses/Neautorizat" },
          "403": { "$ref": "#/components/responses/AdresaNepermisa" },
          "404": { "$ref": "#/components/responses/NegasitProforma" }
        }
      },
      "put": {
        "tags": ["Proforme"],
        "summary": "Modifică o proformă",
        "description": "Necesită dreptul `write`. Se schimbă doar câmpurile trimise. Nu se mai poate modifica după conversia în factură sau după anulare: documentul emis și cel care l-a justificat ar ajunge să spună lucruri diferite.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": { "schema": { "$ref": "#/components/schemas/ActualizareProforma" } }
          }
        },
        "responses": {
          "200": {
            "description": "Proforma actualizată",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Proforma" } } }
          },
          "400": { "$ref": "#/components/responses/DateInvalide" },
          "401": { "$ref": "#/components/responses/Neautorizat" },
          "403": { "$ref": "#/components/responses/DoarCitire" },
          "404": { "$ref": "#/components/responses/NegasitProforma" },
          "409": {
            "description": "Proforma a fost deja convertită în factură sau este anulată.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Eroare" } } }
          }
        }
      }
    },
    "/proformas/{id}/pdf": {
      "parameters": [{ "$ref": "#/components/parameters/IdProforma" }],
      "get": {
        "tags": ["Proforme"],
        "summary": "PDF-ul proformei",
        "description": "Documentul proformei, cu datele furnizorului (inclusiv conturile bancare), ale clientului, liniile și totalul — același desen ca în interfață. Proforma nu are XML și nu trece prin ANAF, deci PDF-ul se generează pe loc, din datele ei de acum. O proformă emisă în mediul de test poartă filigranul „TEST”, iar una anulată, „ANULATĂ”. Cere autentificare, deci nu poate fi folosit ca link public: proxy-ați-l prin aplicația dumneavoastră.",
        "responses": {
          "200": {
            "description": "Fișierul PDF",
            "content": { "application/pdf": { "schema": { "type": "string", "format": "binary" } } }
          },
          "401": { "$ref": "#/components/responses/Neautorizat" },
          "403": { "$ref": "#/components/responses/AdresaNepermisa" },
          "404": { "$ref": "#/components/responses/NegasitProforma" }
        }
      }
    },
    "/proformas/{id}/anulare": {
      "parameters": [{ "$ref": "#/components/parameters/IdProforma" }],
      "post": {
        "tags": ["Proforme"],
        "summary": "Anulează o proformă",
        "description": "Necesită dreptul `write`. O proformă deja convertită nu se anulează: se stornează factura rezultată.",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": { "motiv": { "type": "string", "maxLength": 300 } }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Proforma anulată",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Proforma" } } }
          },
          "401": { "$ref": "#/components/responses/Neautorizat" },
          "403": { "$ref": "#/components/responses/DoarCitire" },
          "404": { "$ref": "#/components/responses/NegasitProforma" },
          "409": {
            "description": "Proforma e deja anulată sau a fost convertită în factură.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Eroare" } } }
          }
        }
      }
    },
    "/proformas/{id}/to-invoice": {
      "parameters": [{ "$ref": "#/components/parameters/IdProforma" }],
      "post": {
        "tags": ["Proforme"],
        "summary": "Convertește proforma în factură și o depune",
        "description": "Necesită dreptul `write`. Factura preia clientul, liniile și totalurile proformei, primește număr din seria de facturi și urmează drumul obișnuit către SPV. Moștenește mediul proformei: una de probă nu poate produce o factură reală.\n\nLegătura se scrie înainte de depunere, deci chiar dacă depunerea eșuează proforma rămâne marcată drept convertită — o a doua încercare nu mai emite încă o factură.",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "externalId": { "type": "string", "maxLength": 120, "description": "Pentru idempotența facturii rezultate." },
                  "seria": { "type": "string", "maxLength": 20, "description": "Seria de facturi; implicit cea implicită a firmei." },
                  "modPlata": { "type": "string", "maxLength": 30, "default": "transfer" },
                  "depune": { "type": "boolean", "default": true }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Factura a fost creată din proformă",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "duplicat": { "type": "boolean", "const": false },
                    "depunere": {
                      "type": "object",
                      "properties": {
                        "status": { "type": ["string", "null"] },
                        "reusita": { "type": "boolean" },
                        "indexIncarcare": { "type": ["string", "null"] },
                        "mesaj": { "type": "string" }
                      }
                    },
                    "factura": {
                      "type": "object",
                      "properties": {
                        "id": { "type": "string" },
                        "seria": { "type": "string" },
                        "numar": { "type": "integer" },
                        "numarComplet": { "type": "string" },
                        "total": { "type": "number" },
                        "modTest": { "type": "boolean" }
                      }
                    },
                    "proforma": { "$ref": "#/components/schemas/Proforma" }
                  }
                }
              }
            }
          },
          "200": {
            "description": "Există deja o factură cu acel `externalId`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "duplicat": { "type": "boolean", "const": true },
                    "facturaId": { "type": "string" }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Neautorizat" },
          "402": { "$ref": "#/components/responses/LimitaPlan" },
          "403": { "$ref": "#/components/responses/DoarCitire" },
          "404": { "$ref": "#/components/responses/NegasitProforma" },
          "409": {
            "description": "Proforma e deja convertită, e anulată, sau firma nu are serie de facturare.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Eroare" } } }
          }
        }
      }
    },
    "/contracts": {
      "post": {
        "tags": ["Contracte"],
        "summary": "Creează un contract",
        "description": "Necesită dreptul `write`. Contractul nu se depune nicăieri: stă la baza facturilor, care se emit din el printr-o operațiune explicită. Seria e text liber (implicit `CTR`) și nu necesită configurare în aplicație.",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CerereContract" } } }
        },
        "responses": {
          "201": {
            "description": "Contractul a fost creat",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "duplicat": { "type": "boolean", "const": false },
                    "contract": { "$ref": "#/components/schemas/Contract" }
                  }
                }
              }
            }
          },
          "200": {
            "description": "Există deja un contract cu acest `externalId` în mediul curent.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "duplicat": { "type": "boolean", "const": true },
                    "contract": { "$ref": "#/components/schemas/Contract" }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/DateInvalide" },
          "401": { "$ref": "#/components/responses/Neautorizat" },
          "403": { "$ref": "#/components/responses/DoarCitire" },
          "429": { "$ref": "#/components/responses/PreaMulteCereri" }
        }
      },
      "get": {
        "tags": ["Contracte"],
        "summary": "Listă paginată de contracte",
        "parameters": [
          { "name": "externalId", "in": "query", "schema": { "type": "string" } },
          { "name": "stare", "in": "query", "schema": { "$ref": "#/components/schemas/StareContract" } },
          { "name": "from", "in": "query", "description": "Data contractului, de la.", "schema": { "type": "string", "format": "date" } },
          { "name": "to", "in": "query", "description": "Data contractului, până la.", "schema": { "type": "string", "format": "date" } },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 50, "minimum": 1, "maximum": 200 } },
          { "name": "offset", "in": "query", "schema": { "type": "integer", "default": 0, "minimum": 0 } }
        ],
        "responses": {
          "200": {
            "description": "Lista contractelor",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "total": { "type": "integer" },
                    "limit": { "type": "integer" },
                    "offset": { "type": "integer" },
                    "contracte": { "type": "array", "items": { "$ref": "#/components/schemas/Contract" } }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Neautorizat" },
          "403": { "$ref": "#/components/responses/AdresaNepermisa" }
        }
      }
    },
    "/contracts/{id}": {
      "parameters": [{ "$ref": "#/components/parameters/IdContract" }],
      "get": {
        "tags": ["Contracte"],
        "summary": "Returnează un contract",
        "responses": {
          "200": {
            "description": "Contractul",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Contract" } } }
          },
          "401": { "$ref": "#/components/responses/Neautorizat" },
          "403": { "$ref": "#/components/responses/AdresaNepermisa" },
          "404": { "$ref": "#/components/responses/NegasitContract" }
        }
      },
      "put": {
        "tags": ["Contracte"],
        "summary": "Modifică un contract",
        "description": "Necesită dreptul `write`. Se schimbă doar câmpurile trimise. Obiectul `dpa` se ÎMBINĂ cu cel existent, deci se pot trimite doar câmpurile modificate. Un contract anulat nu mai poate fi modificat.",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ActualizareContract" } } }
        },
        "responses": {
          "200": {
            "description": "Contractul actualizat",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Contract" } } }
          },
          "400": { "$ref": "#/components/responses/DateInvalide" },
          "401": { "$ref": "#/components/responses/Neautorizat" },
          "403": { "$ref": "#/components/responses/DoarCitire" },
          "404": { "$ref": "#/components/responses/NegasitContract" },
          "409": {
            "description": "Contractul este anulat.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Eroare" } } }
          }
        }
      }
    },
    "/contracts/{id}/anulare": {
      "parameters": [{ "$ref": "#/components/parameters/IdContract" }],
      "post": {
        "tags": ["Contracte"],
        "summary": "Anulează un contract",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": { "type": "object", "properties": { "motiv": { "type": "string", "maxLength": 300 } } }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Contractul anulat",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Contract" } } }
          },
          "401": { "$ref": "#/components/responses/Neautorizat" },
          "403": { "$ref": "#/components/responses/DoarCitire" },
          "404": { "$ref": "#/components/responses/NegasitContract" },
          "409": {
            "description": "Contractul este deja anulat.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Eroare" } } }
          }
        }
      }
    },
    "/contracts/{id}/docx": {
      "parameters": [{ "$ref": "#/components/parameters/IdContract" }],
      "get": {
        "tags": ["Contracte"],
        "summary": "Contractul ca document Word",
        "description": "Generat la cerere din datele curente, deci mereu actualizat. Formatul Word e intenționat: părțile ajustează textul înainte de semnare.",
        "responses": {
          "200": {
            "description": "Fișierul .docx",
            "content": {
              "application/vnd.openxmlformats-officedocument.wordprocessingml.document": {
                "schema": { "type": "string", "format": "binary" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Neautorizat" },
          "403": { "$ref": "#/components/responses/AdresaNepermisa" },
          "404": { "$ref": "#/components/responses/NegasitContract" }
        }
      }
    },
    "/contracts/{id}/dpa": {
      "parameters": [{ "$ref": "#/components/parameters/IdContract" }],
      "get": {
        "tags": ["Contracte"],
        "summary": "Acordul de prelucrare a datelor (art. 28 GDPR), ca document Word",
        "description": "Se generează doar dacă `dpa.activ` este adevărat. Rolurile se stabilesc din `dpa.rolFirma`: firma poate fi împuternicit (prestează un serviciu care atinge datele clientului) sau operator. Documentul cuprinde cele 11 articole, Anexa 1 (descrierea prelucrării) și Anexa 2 (măsurile tehnice și organizatorice).",
        "responses": {
          "200": {
            "description": "Fișierul .docx",
            "content": {
              "application/vnd.openxmlformats-officedocument.wordprocessingml.document": {
                "schema": { "type": "string", "format": "binary" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Neautorizat" },
          "403": { "$ref": "#/components/responses/AdresaNepermisa" },
          "404": { "$ref": "#/components/responses/NegasitContract" },
          "409": {
            "description": "Acordul nu este activat pe acest contract (`dpa.activ` este fals).",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Eroare" } } }
          }
        }
      }
    },
    "/contracts/{id}/emite-factura": {
      "parameters": [{ "$ref": "#/components/parameters/IdContract" }],
      "post": {
        "tags": ["Contracte"],
        "summary": "Emite o factură în baza contractului",
        "description": "Necesită dreptul `write`. Operațiune explicită: se cheamă când e de emis. Fără `linii`, se folosesc cele din contract. Scadența se calculează din `termenPlataZile`, iar factura moștenește mediul contractului și este legată de el.",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "externalId": { "type": "string", "maxLength": 120, "description": "Pentru idempotența facturii; ex. `ctr12_2026_10`." },
                  "seria": { "type": "string", "maxLength": 20, "description": "Seria de facturi; implicit cea implicită a firmei." },
                  "dataEmitere": { "type": "string", "format": "date" },
                  "modPlata": { "type": "string", "maxLength": 30, "default": "transfer" },
                  "observatii": { "type": ["string", "null"], "maxLength": 2000 },
                  "linii": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 500,
                    "description": "Pozițiile facturate. Absente, se preiau cele din contract.",
                    "items": { "$ref": "#/components/schemas/LinieCerere" }
                  },
                  "depune": { "type": "boolean", "default": true }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Factura a fost emisă",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "duplicat": { "type": "boolean", "const": false },
                    "depunere": {
                      "type": "object",
                      "properties": {
                        "status": { "type": ["string", "null"] },
                        "reusita": { "type": "boolean" },
                        "indexIncarcare": { "type": ["string", "null"] },
                        "mesaj": { "type": "string" }
                      }
                    },
                    "factura": {
                      "type": "object",
                      "properties": {
                        "id": { "type": "string" },
                        "seria": { "type": "string" },
                        "numar": { "type": "integer" },
                        "numarComplet": { "type": "string" },
                        "dataEmitere": { "type": "string", "format": "date" },
                        "dataScadenta": { "type": ["string", "null"], "format": "date" },
                        "total": { "type": "number" },
                        "modTest": { "type": "boolean" }
                      }
                    },
                    "contract": { "$ref": "#/components/schemas/Contract" }
                  }
                }
              }
            }
          },
          "200": {
            "description": "Există deja o factură cu acel `externalId`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "duplicat": { "type": "boolean", "const": true },
                    "facturaId": { "type": "string" }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Contractul nu are linii, iar cererea nu conține `linii`.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Eroare" } } }
          },
          "401": { "$ref": "#/components/responses/Neautorizat" },
          "402": { "$ref": "#/components/responses/LimitaPlan" },
          "403": { "$ref": "#/components/responses/DoarCitire" },
          "404": { "$ref": "#/components/responses/NegasitContract" },
          "409": {
            "description": "Contractul e anulat sau firma nu are serie de facturare.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Eroare" } } }
          }
        }
      }
    },
    "/spv/sincronizare": {
      "post": {
        "tags": ["SPV"],
        "summary": "Aduce mesajele firmei din SPV",
        "description": "Cere de la ANAF toate mesajele firmei din ultimele `zile` zile — facturi primite și trimise, erori, mesaje de la cumpărători —, pagină cu pagină, și le salvează. Mesajele deja aduse (prin API sau din aplicație) nu se dublează. Cel mult o sincronizare pe minut pentru o firmă. Mediul e al cheii: o cheie de test, sau o firmă comutată pe test, citește SPV-ul de test al ANAF. Nu cere dreptul `write`.",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "zile": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 60,
                    "default": 60,
                    "description": "Câte zile în urmă. ANAF dă mesajele din cel mult ultimele 60 de zile."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Rezultatul sincronizării",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RezultatSincronizare" } } }
          },
          "400": { "$ref": "#/components/responses/DateInvalide" },
          "401": { "$ref": "#/components/responses/Neautorizat" },
          "403": { "$ref": "#/components/responses/AdresaNepermisa" },
          "404": { "$ref": "#/components/responses/NegasitFirma" },
          "409": {
            "description": "Firma nu e autorizată la ANAF sau autorizarea nu a putut fi reînnoită.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Eroare" } } }
          },
          "429": {
            "description": "Firma a mai fost sincronizată în ultimul minut, sau cheia a trecut de 240 de cereri pe minut. Antetul `Retry-After` spune peste câte secunde puteți relua.",
            "headers": {
              "Retry-After": { "schema": { "type": "integer" }, "description": "Secunde până la reluare." }
            },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Eroare" } } }
          },
          "502": { "$ref": "#/components/responses/EroareAnaf" }
        }
      }
    },
    "/spv/mesaje": {
      "get": {
        "tags": ["SPV"],
        "summary": "Mesajele salvate, cele mai noi primele",
        "description": "Lista mesajelor aduse din SPV, fără niciun apel la ANAF. O cheie vede doar mesajele firmei ei, din mediul ei; cele aduse din aplicație sunt de producție.",
        "parameters": [
          {
            "name": "tip",
            "in": "query",
            "description": "Doar un tip de mesaje.",
            "schema": { "type": "string", "enum": ["primite", "trimise", "erori", "mesaje"] }
          },
          {
            "name": "from",
            "in": "query",
            "description": "Ziua mesajului (calendarul României), de la (inclusiv).",
            "schema": { "type": "string", "format": "date" }
          },
          {
            "name": "to",
            "in": "query",
            "description": "Ziua mesajului, până la (inclusiv).",
            "schema": { "type": "string", "format": "date" }
          },
          {
            "name": "cautare",
            "in": "query",
            "description": "Text căutat, fără diferență între litere mari și mici, în emitent, beneficiar, numărul facturii, CIF-uri și detalii. E căutat literal: caracterele speciale nu sunt operatori.",
            "schema": { "type": "string", "maxLength": 100 }
          },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 50, "minimum": 1, "maximum": 200 } },
          { "name": "offset", "in": "query", "schema": { "type": "integer", "default": 0, "minimum": 0 } }
        ],
        "responses": {
          "200": {
            "description": "Lista mesajelor",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "total": { "type": "integer" },
                    "limit": { "type": "integer" },
                    "offset": { "type": "integer" },
                    "sincronizatLa": {
                      "type": ["string", "null"],
                      "format": "date-time",
                      "description": "Ultima sincronizare prin API, în mediul cheii; `null` dacă n-a fost niciuna."
                    },
                    "mesaje": { "type": "array", "items": { "$ref": "#/components/schemas/Mesaj" } }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`tip`, `from`, `to` sau `cautare` (peste 100 de caractere) invalide.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Eroare" } } }
          },
          "401": { "$ref": "#/components/responses/Neautorizat" },
          "403": { "$ref": "#/components/responses/AdresaNepermisa" },
          "429": { "$ref": "#/components/responses/PreaMulteCereri" }
        }
      }
    },
    "/spv/mesaje/{id}": {
      "parameters": [{ "$ref": "#/components/parameters/IdMesaj" }],
      "get": {
        "tags": ["SPV"],
        "summary": "Un mesaj",
        "responses": {
          "200": {
            "description": "Mesajul",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Mesaj" } } }
          },
          "400": { "$ref": "#/components/responses/DateInvalide" },
          "401": { "$ref": "#/components/responses/Neautorizat" },
          "403": { "$ref": "#/components/responses/AdresaNepermisa" },
          "404": { "$ref": "#/components/responses/NegasitMesaj" }
        }
      }
    },
    "/spv/mesaje/{id}/descarcare": {
      "parameters": [{ "$ref": "#/components/parameters/IdMesaj" }],
      "post": {
        "tags": ["SPV"],
        "summary": "Descarcă mesajul de la ANAF și îl citește",
        "description": "Descarcă arhiva de la ANAF, dacă nu e deja pe server, o dezarhivează și citește factura (emitent, beneficiar, număr, dată, valoare, CIF-uri) sau textul erorii; pentru facturi face și PDF-ul (transformarea ANAF). Arhiva se descarcă o singură dată — ANAF permite 10 descărcări pe zi pentru un mesaj —, apoi totul se citește de pe disc.",
        "responses": {
          "200": {
            "description": "Mesajul, cu datele citite",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Mesaj" } } }
          },
          "400": { "$ref": "#/components/responses/DateInvalide" },
          "401": { "$ref": "#/components/responses/Neautorizat" },
          "403": { "$ref": "#/components/responses/AdresaNepermisa" },
          "404": { "$ref": "#/components/responses/NegasitMesaj" },
          "409": {
            "description": "Firma nu e autorizată la ANAF.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Eroare" } } }
          },
          "429": { "$ref": "#/components/responses/PreaMulteCereri" },
          "502": { "$ref": "#/components/responses/EroareAnaf" }
        }
      }
    },
    "/spv/mesaje/{id}/pdf": {
      "parameters": [{ "$ref": "#/components/parameters/IdMesaj" }],
      "get": {
        "tags": ["SPV"],
        "summary": "PDF-ul facturii din mesaj",
        "description": "Redarea ANAF a facturii primite sau trimise. Dacă mesajul nu e descărcat, îl descarcă întâi. Mesajele de eroare și cele de la cumpărători nu au PDF (404): folosiți `/xml`.",
        "responses": {
          "200": {
            "description": "Fișierul PDF",
            "content": { "application/pdf": { "schema": { "type": "string", "format": "binary" } } }
          },
          "400": { "$ref": "#/components/responses/DateInvalide" },
          "401": { "$ref": "#/components/responses/Neautorizat" },
          "403": { "$ref": "#/components/responses/AdresaNepermisa" },
          "404": {
            "description": "Mesajul nu există, nu e al cheii, sau nu are PDF (mesaj de eroare, mesaj de la cumpărător).",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Eroare" } } }
          },
          "409": {
            "description": "Firma nu e autorizată la ANAF (descărcarea nu se poate face).",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Eroare" } } }
          },
          "502": { "$ref": "#/components/responses/EroareAnaf" }
        }
      }
    },
    "/spv/mesaje/{id}/xml": {
      "parameters": [{ "$ref": "#/components/parameters/IdMesaj" }],
      "get": {
        "tags": ["SPV"],
        "summary": "XML-ul din mesaj",
        "description": "Factura UBL, sau XML-ul erorii la un mesaj de eroare, fără semnătura ANAF. Dacă mesajul nu e descărcat, îl descarcă întâi.",
        "responses": {
          "200": {
            "description": "Fișierul XML",
            "content": { "application/xml": { "schema": { "type": "string" } } }
          },
          "400": { "$ref": "#/components/responses/DateInvalide" },
          "401": { "$ref": "#/components/responses/Neautorizat" },
          "403": { "$ref": "#/components/responses/AdresaNepermisa" },
          "404": { "$ref": "#/components/responses/NegasitMesaj" },
          "409": {
            "description": "Firma nu e autorizată la ANAF (descărcarea nu se poate face).",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Eroare" } } }
          },
          "502": { "$ref": "#/components/responses/EroareAnaf" }
        }
      }
    },
    "/spv/mesaje/{id}/zip": {
      "parameters": [{ "$ref": "#/components/parameters/IdMesaj" }],
      "get": {
        "tags": ["SPV"],
        "summary": "Arhiva ANAF a mesajului",
        "description": "Arhiva exact cum a dat-o ANAF, cu semnătura (`semnatura_*.xml`). Dacă mesajul nu e descărcat, îl descarcă întâi.",
        "responses": {
          "200": {
            "description": "Arhiva ZIP",
            "content": { "application/zip": { "schema": { "type": "string", "format": "binary" } } }
          },
          "400": { "$ref": "#/components/responses/DateInvalide" },
          "401": { "$ref": "#/components/responses/Neautorizat" },
          "403": { "$ref": "#/components/responses/AdresaNepermisa" },
          "404": { "$ref": "#/components/responses/NegasitMesaj" },
          "409": {
            "description": "Firma nu e autorizată la ANAF (descărcarea nu se poate face).",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Eroare" } } }
          },
          "502": { "$ref": "#/components/responses/EroareAnaf" }
        }
      }
    },
    "/spv/descarcare": {
      "post": {
        "tags": ["SPV"],
        "summary": "Descarcă mesajele încă nedescărcate",
        "description": "Descarcă cel mult `limita` mesaje încă nedescărcate, cele mai noi primele, câte trei deodată, fiecare ca `POST /spv/mesaje/{id}/descarcare`. Un mesaj care nu a putut fi descărcat apare în `erori` și stă deoparte 10 minute, ca loturile următoare să treacă la celelalte. Repetați cât timp `ramase` > 0 și `descarcate` > 0.",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "tip": { "type": "string", "enum": ["primite", "trimise", "erori", "mesaje"], "description": "Doar un tip de mesaje." },
                  "limita": { "type": "integer", "minimum": 1, "maximum": 50, "default": 20 }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Rezultatul lotului",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RezultatDescarcareLot" } } }
          },
          "400": { "$ref": "#/components/responses/DateInvalide" },
          "401": { "$ref": "#/components/responses/Neautorizat" },
          "403": { "$ref": "#/components/responses/AdresaNepermisa" },
          "404": { "$ref": "#/components/responses/NegasitFirma" },
          "409": {
            "description": "Firma nu e autorizată la ANAF.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Eroare" } } }
          },
          "429": { "$ref": "#/components/responses/PreaMulteCereri" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "cheieApi": {
        "type": "http",
        "scheme": "bearer",
        "description": "Cheia se creează din aplicație, la Setări → API și integrări. Se acceptă și antetul `X-API-Key: efk_…`. Numai server-to-server: rutele nu au CORS activat. O cheie poate fi restrânsă la anumite adrese IP (IPv4, IPv6, CIDR); de la altă adresă, cererea primește 403."
      }
    },
    "parameters": {
      "IdFactura": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "Identificatorul facturii returnat la creare.",
        "schema": { "type": "string", "pattern": "^[0-9a-f]{24}$" }
      },
      "IdProforma": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "Identificatorul proformei returnat la creare.",
        "schema": { "type": "string", "pattern": "^[0-9a-f]{24}$" }
      },
      "IdContract": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "Identificatorul contractului returnat la creare.",
        "schema": { "type": "string", "pattern": "^[0-9a-f]{24}$" }
      },
      "IdMesaj": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "Identificatorul mesajului: câmpul `id` din `GET /spv/mesaje`.",
        "schema": { "type": "string", "pattern": "^[0-9a-f]{24}$" }
      }
    },
    "schemas": {
      "TextUtf8": {
        "type": "string",
        "description": "Text UTF-8. Respins cu 400 dacă include caracterul de înlocuire U+FFFD, semn că a fost trimis în alt set de caractere."
      },
      "StareDepunere": {
        "type": ["string", "null"],
        "enum": [null, "nedepusa", "eroare_xml", "fara_curs", "eroare_validare", "eroare_depunere", "validata", "incarcata", "ok", "nok"],
        "description": "`null` — există local, netrimisă. `nedepusa` — creată cu `depune: false`. `eroare_xml` — XML-ul nu s-a putut genera. `fara_curs` — doar în răspunsul la creare: factură în valută fără curs BNR disponibil, deci fără XML și nedepusă. `eroare_validare` — respinsă de validatorul ANAF. `eroare_depunere` — firma nu e autorizată, token expirat sau ANAF indisponibil. `validata` — a trecut validarea, dar NU a fost încă încărcată. `incarcata` — trimisă, în prelucrare. `ok` / `nok` — stări finale."
      },
      "RezultatValidare": {
        "type": ["object", "null"],
        "properties": {
          "valid": { "type": "boolean" },
          "mesaj": { "type": "string", "description": "La respingere, mesajul ANAF cu regula încălcată (ex. BR-RO-110)." }
        }
      },
      "Client": {
        "type": "object",
        "description": "Cel puțin unul dintre `cui`, `denumire` sau `externalId`. Pentru clienții din România, `adresa`, `localitate` și `judet` sunt obligatorii: ANAF respinge facturile fără adresa completă a cumpărătorului. Clientul găsit după `externalId` primește datele trimise aici; adresa, `codPostal`, `email` și codurile care lipsesc se golesc.",
        "properties": {
          "externalId": {
            "type": "string",
            "maxLength": 120,
            "description": "Identificatorul clientului la dumneavoastră. Obligatoriu în practică pentru persoane fizice: fără CUI, potrivirea cade pe denumire, iar doi omonimi ar fi contopiți."
          },
          "cui": {
            "type": "string",
            "maxLength": 20,
            "description": "CUI-ul unei firme românești (cu sau fără `RO`), ori codul de TVA sau de înregistrare al unei firme străine, păstrat întreg. Registrul ANAF se întreabă doar pentru clienții din România.",
            "examples": ["RO12345678", "DE123456789"]
          },
          "denumire": { "allOf": [{ "$ref": "#/components/schemas/TextUtf8" }], "maxLength": 200 },
          "adresa": { "allOf": [{ "$ref": "#/components/schemas/TextUtf8" }], "maxLength": 300 },
          "localitate": { "allOf": [{ "$ref": "#/components/schemas/TextUtf8" }], "maxLength": 100 },
          "judet": {
            "allOf": [{ "$ref": "#/components/schemas/TextUtf8" }],
            "maxLength": 100,
            "description": "Pentru România: denumirea județului sau codul ISO 3166-2:RO, convertite automat în codul cerut de CIUS-RO (BT-54). În afara României: statul sau regiunea, așa cum e (ex. `TX`, `BC`).",
            "examples": ["Bistrița-Năsăud", "RO-BN"]
          },
          "codPostal": { "allOf": [{ "$ref": "#/components/schemas/TextUtf8" }], "maxLength": 20, "description": "Codul poștal (BT-53).", "examples": ["400015", "10117"] },
          "tara": {
            "type": "string",
            "pattern": "^[A-Za-z]{2}$",
            "default": "RO",
            "description": "Codul ISO 3166-1 alpha-2, în orice scriere. Marea Britanie e `GB`, Grecia `GR`.",
            "examples": ["RO", "DE", "us"]
          },
          "codInregistrare": { "allOf": [{ "$ref": "#/components/schemas/TextUtf8" }], "maxLength": 50, "examples": ["J06/123/2020"] },
          "email": { "type": "string", "maxLength": 150 },
          "telefon": { "type": "string", "maxLength": 50 },
          "iban": { "type": "string", "maxLength": 40 },
          "banca": { "allOf": [{ "$ref": "#/components/schemas/TextUtf8" }], "maxLength": 100 },
          "platitorTva": { "type": "boolean", "default": true }
        }
      },
      "LinieCerere": {
        "type": "object",
        "required": ["denumire", "cantitate", "pretUnitar"],
        "properties": {
          "denumire": { "allOf": [{ "$ref": "#/components/schemas/TextUtf8" }], "minLength": 1, "maxLength": 300 },
          "cantitate": { "type": "number" },
          "pretUnitar": {
            "type": "number",
            "description": "Preț FĂRĂ TVA. TVA-ul se adaugă peste: 700 cu cota 21 dau un total de 847. Pentru prețuri afișate final, împărțiți înainte."
          },
          "cotaTva": {
            "oneOf": [{ "type": "string", "maxLength": 10 }, { "type": "number" }],
            "default": "21",
            "description": "`21`, `19`, `11`, `9`, `5`, `0`, ori `E` (scutit) sau `AE` (taxare inversă). O cotă necunoscută e respinsă cu 400, nu înlocuită cu valoarea implicită. La o firmă neplătitoare de TVA, cota trimisă se ignoră: linia e în categoria `O`, fără TVA; o firmă plătitoare nu poate folosi `O`.",
            "examples": ["21", "E", 9]
          },
          "um": { "allOf": [{ "$ref": "#/components/schemas/TextUtf8" }], "maxLength": 20, "default": "BUC" },
          "umUbl": { "type": "string", "maxLength": 10, "default": "H87", "description": "Codul UN/ECE al unității de măsură." }
        }
      },
      "CerereFactura": {
        "type": "object",
        "required": ["client", "linii"],
        "properties": {
          "externalId": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "description": "Identificatorul comenzii la dumneavoastră. Asigură idempotența: reapelarea întoarce factura existentă, nu creează alta. Spațiul de nume e separat pe medii, deci aceeași valoare poate exista și în test, și în producție.",
            "examples": ["wc_order_1234"]
          },
          "seria": { "type": "string", "minLength": 1, "maxLength": 20, "description": "Implicit, seria marcată ca implicită în aplicație." },
          "dataEmitere": { "type": "string", "format": "date", "description": "Implicit data curentă, în fusul României." },
          "dataScadenta": { "type": ["string", "null"], "format": "date" },
          "moneda": { "type": "string", "pattern": "^[A-Za-z]{3}$", "default": "RON", "description": "Codul ISO 4217, în orice scriere (`usd` devine `USD`)." },
          "cursValutar": {
            "type": "number",
            "exclusiveMinimum": 0,
            "maximum": 100000,
            "description": "Lei pentru o unitate din `moneda` (factură în valută) sau din `monedaPlata` (factură în lei plătită în valută). Lipsă = cursul BNR valabil la data facturii (ultimul publicat înaintea ei), luat de server.",
            "examples": [4.7698]
          },
          "dataCurs": { "type": "string", "format": "date", "description": "Ziua cursului trimis. Doar împreună cu `cursValutar`." },
          "monedaPlata": {
            "type": "string",
            "pattern": "^[A-Za-z]{3}$",
            "description": "Moneda în care a plătit clientul, pentru o factură în lei (`moneda: \"RON\"`). Prețurile liniilor sunt atunci în ea și se trec în lei la cursul BNR valabil la data facturii, sau la `cursValutar`. Fiecare linie se rotunjește la bani; totalul e suma liniilor. Lipsă, sau egală cu `moneda`: cererea e ca înainte. Cu altă monedă decât RON: 400.",
            "examples": ["USD"]
          },
          "modPlata": { "type": "string", "maxLength": 30, "default": "transfer", "enum": ["transfer", "card", "numerar"], "description": "`transfer` cere un IBAN al firmei. Pentru plățile cu cardul (Stripe), `card`." },
          "observatii": { "allOf": [{ "$ref": "#/components/schemas/TextUtf8" }], "maxLength": 2000 },
          "conturiBancare": {
            "type": "array",
            "maxItems": 5,
            "items": {
              "type": "object",
              "properties": { "iban": { "type": "string", "maxLength": 40 }, "banca": { "type": "string", "maxLength": 100 } }
            }
          },
          "client": { "$ref": "#/components/schemas/Client" },
          "linii": { "type": "array", "minItems": 1, "maxItems": 500, "items": { "$ref": "#/components/schemas/LinieCerere" } },
          "scutireTva": {
            "type": "object",
            "description": "Temeiul scutirii, pentru cota `E`. Fără el, XML-ul poartă formularea generică VATEX-EU-79-C, care descrie altceva decât, de pildă, o scutire pentru servicii medicale.",
            "properties": {
              "cod": { "type": "string", "maxLength": 30, "examples": ["VATEX-EU-132-1C"] },
              "motiv": { "allOf": [{ "$ref": "#/components/schemas/TextUtf8" }], "maxLength": 300 }
            }
          },
          "modTest": {
            "type": "boolean",
            "description": "Forțează mediul de test pentru această factură. Poate doar să ACTIVEZE testul: o cheie de test sau o firmă în test rămân în test chiar dacă se trimite `false`."
          },
          "depune": { "type": "boolean", "default": true, "description": "Cu `false`, factura e doar creată; o depuneți ulterior." }
        }
      },
      "Linie": {
        "type": "object",
        "properties": {
          "nrLinie": { "type": "integer" },
          "denumire": { "type": "string" },
          "um": { "type": "string" },
          "cantitate": { "type": "number" },
          "pretUnitar": { "type": "number" },
          "cotaTva": { "type": "string" },
          "valoareLinie": { "type": "number" },
          "valoareTva": { "type": "number" }
        }
      },
      "Factura": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "externalId": { "type": ["string", "null"] },
          "seria": { "type": "string" },
          "numar": { "type": "integer" },
          "numarComplet": { "type": "string", "examples": ["FCT128"] },
          "dataEmitere": { "type": "string", "format": "date" },
          "dataScadenta": { "type": ["string", "null"], "format": "date" },
          "moneda": { "type": "string" },
          "cursValutar": { "type": ["number", "null"], "description": "Lei pentru o unitate din `moneda`. `null` la facturile în lei, sau cât timp cursul n-a putut fi aflat." },
          "dataCurs": { "type": ["string", "null"], "format": "date", "description": "Ziua cursului; la BNR, ziua publicării." },
          "sursaCurs": { "type": ["string", "null"], "enum": ["bnr", "apelant", null] },
          "monedaPlata": { "type": ["string", "null"], "description": "Factura e în lei, plătită în această monedă. `null` = plata e în moneda facturii." },
          "cursPlata": { "type": ["number", "null"], "description": "Lei pentru o unitate din `monedaPlata`: cursul cu care s-au trecut liniile în lei." },
          "dataCursPlata": { "type": ["string", "null"], "format": "date" },
          "sursaCursPlata": { "type": ["string", "null"], "enum": ["bnr", "apelant", null] },
          "totalPlata": { "type": ["number", "null"], "description": "Totalul plătit, în `monedaPlata`, înainte de conversie. Negativ la storno. Totalul în lei e `total`." },
          "modPlata": { "type": "string" },
          "observatii": { "type": ["string", "null"] },
          "scutireTva": {
            "type": ["object", "null"],
            "properties": { "cod": { "type": ["string", "null"] }, "motiv": { "type": ["string", "null"] } }
          },
          "client": {
            "type": ["object", "null"],
            "properties": {
              "id": { "type": "string" },
              "externalId": { "type": ["string", "null"] },
              "denumire": { "type": "string" },
              "cui": { "type": ["string", "null"] },
              "adresa": { "type": ["string", "null"] },
              "localitate": { "type": ["string", "null"] },
              "judet": { "type": ["string", "null"] },
              "codPostal": { "type": ["string", "null"] },
              "tara": { "type": "string", "description": "Codul ISO 3166-1 alpha-2." },
              "email": { "type": ["string", "null"] }
            }
          },
          "linii": { "type": "array", "items": { "$ref": "#/components/schemas/Linie" } },
          "totalFaraTva": { "type": "number" },
          "totalTva": { "type": "number" },
          "total": { "type": "number" },
          "depunere": {
            "type": "object",
            "properties": {
              "status": { "$ref": "#/components/schemas/StareDepunere" },
              "indexIncarcare": { "type": ["string", "null"], "description": "Numărul primit de la ANAF la încărcare. Prezența lui e dovada că factura a ajuns în SPV." },
              "executionStatus": { "type": ["string", "null"] },
              "verificatLa": { "type": ["string", "null"], "format": "date-time" }
            }
          },
          "anulata": { "type": "boolean" },
          "stornata": { "type": "boolean" },
          "modTest": { "type": "boolean" },
          "createdAt": { "type": "string", "format": "date-time" }
        }
      },
      "RaspunsCreare": {
        "type": "object",
        "properties": {
          "duplicat": { "type": "boolean", "const": false },
          "depunere": {
            "type": "object",
            "properties": {
              "status": { "type": ["string", "null"], "description": "Ca `StareDepunere`; în plus `fara_curs`." },
              "reusita": { "type": "boolean" },
              "indexIncarcare": { "type": ["string", "null"] },
              "mesaj": { "type": "string" },
              "validare": { "$ref": "#/components/schemas/RezultatValidare" }
            }
          },
          "factura": { "$ref": "#/components/schemas/Factura" }
        }
      },
      "StareContract": {
        "type": "string",
        "enum": ["proiect", "activ", "incheiat", "anulat"],
        "description": "`proiect` — încă nesemnat. `activ` — în derulare. `incheiat` — a expirat sau s-a finalizat. `anulat` — nu mai produce efecte; nu se mai pot emite facturi în baza lui."
      },
      "Dpa": {
        "type": "object",
        "description": "Acordul de prelucrare a datelor (art. 28 GDPR), atașat contractului ca anexă. Documentul se generează doar dacă `activ` e adevărat.",
        "properties": {
          "activ": { "type": "boolean", "default": false, "description": "Multe contracte nu implică date personale; fără asta, acordul nu se generează." },
          "anexaNr": { "type": "integer", "default": 1, "minimum": 1, "maximum": 99 },
          "dataSemnarii": { "type": ["string", "null"], "format": "date" },
          "rolFirma": {
            "type": "string",
            "enum": ["imputernicit", "operator"],
            "default": "imputernicit",
            "description": "Rolul FIRMEI dumneavoastră. Când prestați un serviciu care atinge datele clientului (găzduire, mentenanță, contabilitate), sunteți împuternicit, iar clientul e operator. Textul acordului se adaptează, nu doar denumirile."
          },
          "obiectPrelucrare": { "type": ["string", "null"], "maxLength": 1000, "description": "Implicit, obiectul contractului." },
          "naturaPrelucrarii": { "type": ["string", "null"], "maxLength": 1000 },
          "scopPrelucrare": { "type": ["string", "null"], "maxLength": 1000 },
          "durataPrelucrarii": { "type": ["string", "null"], "maxLength": 500 },
          "persoaneVizate": { "type": ["string", "null"], "maxLength": 1000 },
          "categoriiDate": { "type": ["string", "null"], "maxLength": 1000 },
          "categoriiSpeciale": {
            "type": ["string", "null"],
            "maxLength": 1000,
            "description": "Datele din art. 9 GDPR (sănătate, biometrice etc.) cer garanții suplimentare și se menționează explicit în Anexa 1."
          },
          "subimputerniciti": {
            "type": "array",
            "maxItems": 20,
            "items": {
              "type": "object",
              "properties": {
                "denumire": { "type": "string", "maxLength": 200 },
                "rol": { "type": "string", "maxLength": 200, "examples": ["Găzduire infrastructură"] },
                "locatie": { "type": "string", "maxLength": 100, "examples": ["Germania (UE)"] }
              }
            }
          },
          "dpoNume": { "type": ["string", "null"], "maxLength": 150 },
          "dpoContact": { "type": ["string", "null"], "maxLength": 200 }
        }
      },
      "CerereContract": {
        "type": "object",
        "required": ["obiect", "client"],
        "properties": {
          "externalId": { "type": "string", "minLength": 1, "maxLength": 120 },
          "seria": { "type": "string", "minLength": 1, "maxLength": 20, "default": "CTR", "description": "Text liber; contractele nu au serii configurate în aplicație." },
          "dataContract": { "type": "string", "format": "date", "description": "Implicit data curentă." },
          "dataInceput": { "type": ["string", "null"], "format": "date" },
          "dataSfarsit": { "type": ["string", "null"], "format": "date", "description": "Absentă, contractul e pe durată nedeterminată." },
          "obiect": { "allOf": [{ "$ref": "#/components/schemas/TextUtf8" }], "maxLength": 4000, "description": "Descrierea serviciilor; apare ca atare în capitolul II al contractului." },
          "moneda": { "type": "string", "pattern": "^[A-Za-z]{3}$", "default": "RON", "description": "Codul ISO 4217, în orice scriere." },
          "client": { "$ref": "#/components/schemas/Client" },
          "linii": { "type": "array", "maxItems": 500, "items": { "$ref": "#/components/schemas/LinieCerere" }, "description": "Pozițiile contractate; devin implicit pozițiile facturilor emise din contract." },
          "reprezentantFirma": { "type": ["string", "null"], "maxLength": 150 },
          "functieFirma": { "type": ["string", "null"], "maxLength": 100, "examples": ["Administrator"] },
          "reprezentantClient": { "type": ["string", "null"], "maxLength": 150 },
          "functieClient": { "type": ["string", "null"], "maxLength": 100, "examples": ["Director"] },
          "conditiiPlata": { "type": ["string", "null"], "maxLength": 2000 },
          "termenPlataZile": { "type": ["integer", "null"], "minimum": 0, "maximum": 365, "description": "Folosit la calcularea scadenței facturilor emise din contract." },
          "frecventaFacturare": {
            "type": ["string", "null"],
            "enum": ["lunar", "trimestrial", "semestrial", "anual", "la_cerere", null],
            "description": "Descrie înțelegerea părților și apare în contract. Emiterea rămâne manuală."
          },
          "ziFacturare": { "type": ["integer", "null"], "minimum": 1, "maximum": 31 },
          "observatii": { "type": ["string", "null"], "maxLength": 2000 },
          "stare": { "$ref": "#/components/schemas/StareContract" },
          "dpa": { "$ref": "#/components/schemas/Dpa" },
          "modTest": { "type": "boolean" }
        }
      },
      "ActualizareContract": {
        "type": "object",
        "description": "Toate câmpurile sunt opționale. `dpa` se îmbină cu cel existent; `linii` înlocuiesc lista, iar valorile se recalculează.",
        "properties": {
          "dataContract": { "type": "string", "format": "date" },
          "dataInceput": { "type": ["string", "null"], "format": "date" },
          "dataSfarsit": { "type": ["string", "null"], "format": "date" },
          "obiect": { "allOf": [{ "$ref": "#/components/schemas/TextUtf8" }], "maxLength": 4000 },
          "moneda": { "type": "string", "pattern": "^[A-Za-z]{3}$" },
          "client": { "$ref": "#/components/schemas/Client" },
          "linii": { "type": "array", "maxItems": 500, "items": { "$ref": "#/components/schemas/LinieCerere" } },
          "reprezentantFirma": { "type": ["string", "null"], "maxLength": 150 },
          "functieFirma": { "type": ["string", "null"], "maxLength": 100 },
          "reprezentantClient": { "type": ["string", "null"], "maxLength": 150 },
          "functieClient": { "type": ["string", "null"], "maxLength": 100 },
          "conditiiPlata": { "type": ["string", "null"], "maxLength": 2000 },
          "termenPlataZile": { "type": ["integer", "null"], "minimum": 0, "maximum": 365 },
          "frecventaFacturare": { "type": ["string", "null"], "enum": ["lunar", "trimestrial", "semestrial", "anual", "la_cerere", null] },
          "ziFacturare": { "type": ["integer", "null"], "minimum": 1, "maximum": 31 },
          "observatii": { "type": ["string", "null"], "maxLength": 2000 },
          "stare": { "$ref": "#/components/schemas/StareContract" },
          "dpa": { "$ref": "#/components/schemas/Dpa" }
        }
      },
      "Contract": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "externalId": { "type": ["string", "null"] },
          "seria": { "type": "string" },
          "numar": { "type": "integer" },
          "numarComplet": { "type": "string", "examples": ["CTR1"] },
          "dataContract": { "type": "string", "format": "date" },
          "dataInceput": { "type": ["string", "null"], "format": "date" },
          "dataSfarsit": { "type": ["string", "null"], "format": "date" },
          "obiect": { "type": "string" },
          "moneda": { "type": "string" },
          "client": {
            "type": ["object", "null"],
            "properties": {
              "id": { "type": "string" },
              "externalId": { "type": ["string", "null"] },
              "denumire": { "type": "string" },
              "cui": { "type": ["string", "null"] },
              "adresa": { "type": ["string", "null"] },
              "localitate": { "type": ["string", "null"] },
              "judet": { "type": ["string", "null"] },
              "codPostal": { "type": ["string", "null"] },
              "tara": { "type": "string", "description": "Codul ISO 3166-1 alpha-2." },
              "email": { "type": ["string", "null"] }
            }
          },
          "linii": { "type": "array", "items": { "$ref": "#/components/schemas/Linie" } },
          "valoareFaraTva": { "type": "number" },
          "valoareTva": { "type": "number" },
          "valoare": { "type": "number" },
          "reprezentantFirma": { "type": ["string", "null"] },
          "functieFirma": { "type": ["string", "null"] },
          "reprezentantClient": { "type": ["string", "null"] },
          "functieClient": { "type": ["string", "null"] },
          "conditiiPlata": { "type": ["string", "null"] },
          "termenPlataZile": { "type": ["integer", "null"] },
          "frecventaFacturare": { "type": ["string", "null"] },
          "ziFacturare": { "type": ["integer", "null"] },
          "observatii": { "type": ["string", "null"] },
          "stare": { "$ref": "#/components/schemas/StareContract" },
          "motivAnulare": { "type": ["string", "null"] },
          "dataAnulare": { "type": ["string", "null"], "format": "date" },
          "facturi": { "type": "array", "items": { "type": "string" }, "description": "Id-urile facturilor emise în baza contractului." },
          "numarFacturiEmise": { "type": "integer" },
          "dpa": { "$ref": "#/components/schemas/Dpa" },
          "modTest": { "type": "boolean" },
          "createdAt": { "type": "string", "format": "date-time" }
        }
      },
      "CerereProforma": {
        "type": "object",
        "required": ["client", "linii"],
        "properties": {
          "externalId": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "description": "Identificatorul documentului la dumneavoastră. Asigură idempotența, cu spațiu de nume separat pe medii."
          },
          "seria": { "type": "string", "minLength": 1, "maxLength": 20, "description": "Serie de PROFORME. Implicit, cea marcată implicită în aplicație." },
          "dataEmitere": { "type": "string", "format": "date", "description": "Implicit data curentă, în fusul României." },
          "dataScadenta": { "type": ["string", "null"], "format": "date" },
          "moneda": { "type": "string", "pattern": "^[A-Za-z]{3}$", "default": "RON", "description": "Codul ISO 4217, în orice scriere." },
          "observatii": { "allOf": [{ "$ref": "#/components/schemas/TextUtf8" }], "maxLength": 2000 },
          "client": { "$ref": "#/components/schemas/Client" },
          "linii": { "type": "array", "minItems": 1, "maxItems": 500, "items": { "$ref": "#/components/schemas/LinieCerere" } },
          "modTest": { "type": "boolean", "description": "Ca la facturi: poate doar să ACTIVEZE mediul de test." }
        }
      },
      "ActualizareProforma": {
        "type": "object",
        "description": "Toate câmpurile sunt opționale; se modifică doar ce se trimite. Liniile trimise înlocuiesc complet lista, iar totalurile se recalculează pe server.",
        "properties": {
          "dataEmitere": { "type": "string", "format": "date" },
          "dataScadenta": { "type": ["string", "null"], "format": "date" },
          "moneda": { "type": "string", "pattern": "^[A-Za-z]{3}$" },
          "observatii": { "allOf": [{ "$ref": "#/components/schemas/TextUtf8" }], "maxLength": 2000 },
          "client": { "$ref": "#/components/schemas/Client" },
          "linii": { "type": "array", "minItems": 1, "maxItems": 500, "items": { "$ref": "#/components/schemas/LinieCerere" } }
        }
      },
      "Proforma": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "externalId": { "type": ["string", "null"] },
          "seria": { "type": "string" },
          "numar": { "type": "integer" },
          "numarComplet": { "type": "string", "examples": ["PRF1"] },
          "dataEmitere": { "type": "string", "format": "date" },
          "dataScadenta": { "type": ["string", "null"], "format": "date" },
          "moneda": { "type": "string" },
          "observatii": { "type": ["string", "null"] },
          "client": {
            "type": ["object", "null"],
            "properties": {
              "id": { "type": "string" },
              "externalId": { "type": ["string", "null"] },
              "denumire": { "type": "string" },
              "cui": { "type": ["string", "null"] },
              "adresa": { "type": ["string", "null"] },
              "localitate": { "type": ["string", "null"] },
              "judet": { "type": ["string", "null"] },
              "codPostal": { "type": ["string", "null"] },
              "tara": { "type": "string", "description": "Codul ISO 3166-1 alpha-2." },
              "email": { "type": ["string", "null"] }
            }
          },
          "linii": { "type": "array", "items": { "$ref": "#/components/schemas/Linie" } },
          "totalFaraTva": { "type": "number" },
          "totalTva": { "type": "number" },
          "total": { "type": "number" },
          "facturaId": { "type": ["string", "null"], "description": "Factura generată din această proformă." },
          "facturata": { "type": "boolean" },
          "anulata": { "type": "boolean" },
          "motivAnulare": { "type": ["string", "null"] },
          "dataAnulare": { "type": ["string", "null"], "format": "date" },
          "modTest": { "type": "boolean" },
          "createdAt": { "type": "string", "format": "date-time" }
        }
      },
      "Firma": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "denumire": { "type": "string" },
          "cui": { "type": "string" },
          "codInregistrare": { "type": ["string", "null"] },
          "adresa": { "type": ["string", "null"] },
          "localitate": { "type": ["string", "null"] },
          "judet": { "type": ["string", "null"] },
          "tipTva": { "type": "string", "enum": ["normal", "la_incasare", "fara_tva", "taxare_inversa"], "description": "`fara_tva`: firmă neplătitoare de TVA; facturile ei ies în categoria O, fără TVA." },
          "modTest": { "type": "boolean", "description": "Modul de test al firmei, setat din aplicație." },
          "mediu": { "type": "string", "enum": ["test", "productie"], "description": "Mediul în care va emite ACEASTĂ cheie." },
          "anaf": {
            "type": "object",
            "properties": {
              "autorizat": { "type": "boolean" },
              "expirat": { "type": ["boolean", "null"] },
              "expiraLa": { "type": ["string", "null"], "format": "date-time", "description": "Autorizarea se reînnoiește automat înainte de acest moment, cât timp firma are token de reînnoire." }
            }
          },
          "serii": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "serie": { "type": "string" },
                "numarCurent": { "type": "integer" },
                "implicita": { "type": "boolean" }
              }
            }
          },
          "abonament": {
            "type": "object",
            "properties": {
              "plan": { "type": "string" },
              "nelimitat": { "type": "boolean" },
              "facturiRamaseLunaAceasta": {
                "type": ["integer", "null"],
                "description": "`null` când planul e nelimitat. Facturile de test nu consumă din această cotă."
              }
            }
          }
        }
      },
      "Eroare": {
        "type": "object",
        "properties": {
          "error": { "type": "string" },
          "details": {
            "type": "array",
            "description": "Prezent la erorile de validare a corpului.",
            "items": {
              "type": "object",
              "properties": { "path": { "type": "string" }, "message": { "type": "string" } }
            }
          }
        }
      },
      "EvenimentWebhook": {
        "type": "object",
        "description": "Corpul notificării trimise la URL-ul configurat pe cheie. Semnat cu HMAC-SHA256 peste `timestamp + \".\" + corpul brut`, în antetul `X-Efact-Signature`.",
        "properties": {
          "event": {
            "type": "string",
            "enum": ["invoice.submitted", "invoice.submit_failed", "invoice.validated", "invoice.rejected"]
          },
          "invoiceId": { "type": "string" },
          "externalId": { "type": ["string", "null"] },
          "seria": { "type": "string" },
          "numar": { "type": "integer" },
          "indexIncarcare": { "type": ["string", "null"] },
          "status": { "type": ["string", "null"] },
          "downloadId": { "type": ["string", "null"] },
          "mesajAnaf": { "type": ["string", "null"] }
        }
      },
      "Rulaj": {
        "type": "object",
        "description": "Sumele sunt în lei, rotunjite la bani. `facturi` și sumele cuprind doar facturile trecute în lei; cele în valută fără curs sunt numărate în `faraCurs`.",
        "properties": {
          "an": { "type": "integer" },
          "moneda": { "type": "string", "enum": ["RON"] },
          "mediu": { "type": "string", "enum": ["test", "productie"], "description": "Mediul facturilor numărate: al cheii." },
          "facturi": { "type": "integer" },
          "totalFaraTva": { "type": "number" },
          "totalTva": { "type": "number" },
          "total": { "type": "number" },
          "faraCurs": { "type": "integer", "description": "Facturi în valută fără curs, lăsate în afara sumelor." },
          "peLuni": {
            "type": "array",
            "description": "Toate cele 12 luni ale anului, în ordine, inclusiv cele fără facturi.",
            "items": {
              "type": "object",
              "properties": {
                "luna": { "type": "string", "examples": ["2026-01"] },
                "facturi": { "type": "integer" },
                "totalFaraTva": { "type": "number" },
                "totalTva": { "type": "number" },
                "total": { "type": "number" }
              }
            }
          },
          "calculatLa": { "type": "string", "format": "date-time" }
        }
      },
      "Mesaj": {
        "type": "object",
        "description": "Un mesaj din SPV. Câmpurile facturii (`emitent`, `numarFactura`, `valoare`…) se completează după descărcare.",
        "properties": {
          "id": { "type": "string" },
          "tip": { "type": "string", "enum": ["primita", "trimisa", "eroare", "mesaj", "altul"] },
          "tipAnaf": { "type": "string", "description": "Tipul exact dat de ANAF, de pildă `FACTURA PRIMITA` sau `ERORI FACTURA`." },
          "idDescarcare": { "type": ["string", "null"], "description": "`id` din lista ANAF, cu care se descarcă arhiva." },
          "indexIncarcare": { "type": ["string", "null"], "description": "`id_solicitare` din lista ANAF: indexul de încărcare al facturii." },
          "dataCreare": { "type": ["string", "null"], "format": "date-time", "description": "Momentul mesajului (`data_creare`, ora României), ca dată UTC." },
          "detalii": { "type": ["string", "null"] },
          "cifEmitent": { "type": ["string", "null"], "description": "Fără prefixul RO. Din XML după descărcare, altfel din `detalii`." },
          "cifBeneficiar": { "type": ["string", "null"], "description": "Fără prefixul RO. Din XML după descărcare, altfel din `detalii`." },
          "emitent": { "type": ["string", "null"] },
          "beneficiar": { "type": ["string", "null"] },
          "numarFactura": { "type": ["string", "null"] },
          "dataFactura": { "type": ["string", "null"], "format": "date" },
          "valoare": { "type": ["number", "null"], "description": "Totalul de plată al facturii (PayableAmount), în moneda ei." },
          "descarcat": { "type": "boolean" },
          "fisiere": {
            "type": "object",
            "properties": { "pdf": { "type": "boolean" }, "xml": { "type": "boolean" }, "zip": { "type": "boolean" } }
          },
          "mesajEroare": { "type": ["string", "null"], "description": "La mesajele de eroare descărcate: erorile date de ANAF, câte una pe rând." }
        }
      },
      "RezultatSincronizare": {
        "type": "object",
        "properties": {
          "total": { "type": "integer", "description": "Câte mesaje a dat ANAF în interval." },
          "noi": { "type": "integer", "description": "Câte dintre ele nu erau încă salvate." },
          "de": { "type": "string", "format": "date-time" },
          "pana": { "type": "string", "format": "date-time" },
          "mediu": { "type": "string", "enum": ["test", "productie"] },
          "sincronizatLa": { "type": "string", "format": "date-time" }
        }
      },
      "RezultatDescarcareLot": {
        "type": "object",
        "properties": {
          "incercate": { "type": "integer" },
          "descarcate": { "type": "integer" },
          "ramase": { "type": "integer", "description": "Mesaje încă nedescărcate după acest lot, inclusiv cele cu eroare." },
          "erori": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": { "id": { "type": "string" }, "mesaj": { "type": "string" } }
            }
          }
        }
      }
    },
    "responses": {
      "Neautorizat": {
        "description": "Cheie lipsă, invalidă, revocată sau expirată.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Eroare" } } }
      },
      "DoarCitire": {
        "description": "Cheia are doar dreptul `read`, iar operațiunea cere `write`. Tot 403 primește o cerere venită de la o adresă IP pe care cheia nu o permite (vezi `AdresaNepermisa`).",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Eroare" } } }
      },
      "DateInvalide": {
        "description": "Payload invalid: câmpuri lipsă, cotă de TVA necunoscută, dată în alt format decât `YYYY-MM-DD`, adresa clientului incompletă sau text care nu e UTF-8.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Eroare" } } }
      },
      "LimitaPlan": {
        "description": "Limita lunară a planului gratuit a fost atinsă. Facturile de test nu consumă din cotă.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Eroare" } } }
      },
      "Conflict": {
        "description": "Firma nu e autorizată la ANAF, autorizarea nu a putut fi reînnoită, factura e deja depusă sau deja stornată.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Eroare" } } }
      },
      "NegasitFactura": {
        "description": "Factura nu există sau aparține altei firme ori altui mediu decât cel al cheii.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Eroare" } } }
      },
      "NegasitContract": {
        "description": "Contractul nu există sau aparține altei firme ori altui mediu decât cel al cheii.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Eroare" } } }
      },
      "NegasitProforma": {
        "description": "Proforma nu există sau aparține altei firme ori altui mediu decât cel al cheii.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Eroare" } } }
      },
      "NegasitFirma": {
        "description": "Firma asociată cheii nu mai există.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Eroare" } } }
      },
      "AdresaNepermisa": {
        "description": "Cheia are o listă de adrese IP permise, iar cererea vine de la altă adresă (sau de la una care nu a putut fi stabilită). Cheia e validă, deci nu 401; adăugați adresa la cheie, în aplicație (Integrări). Mesajul numește adresa văzută de server.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Eroare" } } }
      },
      "NegasitMesaj": {
        "description": "Mesajul nu există sau aparține altei firme ori altui mediu decât cel al cheii.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Eroare" } } }
      },
      "EroareAnaf": {
        "description": "ANAF nu a răspuns sau a răspuns cu eroare (lista de mesaje, descărcarea, starea facturii, transformarea în PDF). `error` spune motivul; reîncercați mai târziu.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Eroare" } } }
      },
      "PreaMulteCereri": {
        "description": "Peste 240 de cereri pe minut pentru aceeași cheie. Antetul `Retry-After` spune peste câte secunde puteți relua.",
        "headers": {
          "Retry-After": { "schema": { "type": "integer" }, "description": "Secunde până la reluare." }
        },
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Eroare" } } }
      }
    }
  },
  "webhooks": {
    "invoice.submitted": {
      "post": {
        "summary": "Factura a fost încărcată la ANAF",
        "requestBody": {
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvenimentWebhook" } } }
        },
        "responses": { "2XX": { "description": "Confirmat. Orice alt cod programează o reîncercare." } }
      }
    },
    "invoice.submit_failed": {
      "post": {
        "summary": "Încărcarea la ANAF a eșuat",
        "requestBody": {
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvenimentWebhook" } } }
        },
        "responses": { "2XX": { "description": "Confirmat." } }
      }
    },
    "invoice.validated": {
      "post": {
        "summary": "ANAF a validat factura",
        "requestBody": {
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvenimentWebhook" } } }
        },
        "responses": { "2XX": { "description": "Confirmat." } }
      }
    },
    "invoice.rejected": {
      "post": {
        "summary": "ANAF a respins factura",
        "requestBody": {
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvenimentWebhook" } } }
        },
        "responses": { "2XX": { "description": "Confirmat." } }
      }
    }
  }
}
