{
    "service": "zugferd-api",
    "auth": "Authorization: Bearer <api-key>  (Keys im Dashboard erstellen; Scope \"Nur lesend\" beschränkt einen Key auf GET-Endpunkte)",
    "companies": "Ein Key gehört einer Firma. Mehrere Firmen einer Gruppe: Header \"X-Company: <Kürzel | Konto-ID | USt-IdNr.>\" (bei JSON auch das Feld \"company\") spricht eine andere Firma an; nur mit Gruppenrecht des Keys (sonst 403), unbekannt, mehrdeutig oder widersprüchlich -> 422. Firmenliste: GET /v1/companies.",
    "billing": "Gewichtetes Credit-Modell: Standard 1, /v1/validate/fraud 3 Credits. Abgelehnte Aufrufe kosten nichts: bei 5xx und bei 422 (Daten nicht EN-16931-konform) werden die verbrauchten Credits inkl. Aufschlaegen automatisch erstattet. Ein geliefertes Ergebnis wird dagegen berechnet, auch wenn es negativ ausfaellt -- eine Pruefung mit dem Ergebnis \"nicht konform\" ist das bestellte Ergebnis (HTTP 200, valid:false).",
    "fields": {
        "lines[]": "item_id (BT-155), buyer_item_id (BT-156), gtin (BT-157, Pruefziffer), list_price + price_discount (BT-148/147, unit_price = Differenz), price_base_qty (BT-149), origin_country (BT-159), note (BT-127), unit (BT-130, Code oder deutsches Wort)",
        "invoice": "project_ref + project_name (BT-11), seller_order_ref (BT-14), payment.reference (BT-83, Standard = Rechnungsnummer), payment.bic (BT-86)",
        "seller/buyer": "street2 (BT-36/51 Adresszusatz), state (BT-39/54 Bundesland)",
        "units": {
            "C62": "Einheiten",
            "H87": "Stück",
            "EA": "Stück (each)",
            "PCE": "Stück (piece)",
            "NAR": "Anzahl Artikel",
            "PR": "Paar",
            "SET": "Set",
            "LS": "Pauschale",
            "P1": "Prozent",
            "E48": "Leistungseinheit (service unit)",
            "E49": "Arbeitstag",
            "NPR": "Anzahl Paare",
            "MIN": "Minuten",
            "HUR": "Stunden",
            "DAY": "Tage",
            "WEE": "Wochen",
            "MON": "Monate",
            "ANN": "Jahre",
            "SEC": "Sekunden",
            "QAN": "Quartale",
            "MGM": "Milligramm",
            "GRM": "Gramm",
            "KGM": "Kilogramm",
            "TNE": "Tonnen",
            "MLT": "Milliliter",
            "LTR": "Liter",
            "HLT": "Hektoliter",
            "MTQ": "Kubikmeter",
            "CMQ": "Kubikzentimeter",
            "MMT": "Millimeter",
            "CMT": "Zentimeter",
            "MTR": "Meter",
            "KMT": "Kilometer",
            "MTK": "Quadratmeter",
            "CMK": "Quadratzentimeter",
            "MMK": "Quadratmillimeter",
            "HAR": "Hektar",
            "KWH": "Kilowattstunden",
            "MWH": "Megawattstunden",
            "KWT": "Kilowatt",
            "WHR": "Wattstunden",
            "XPK": "Paket",
            "XPP": "Stück unverpackt",
            "XBX": "Box",
            "XCT": "Karton",
            "XPA": "Päckchen",
            "XBG": "Beutel",
            "XRO": "Rolle",
            "XBO": "Flasche",
            "XCA": "Dose",
            "XTU": "Tube",
            "XPX": "Palette",
            "XCS": "Kiste",
            "XCR": "Kasten",
            "XSA": "Sack",
            "XCY": "Zylinder",
            "XBJ": "Eimer",
            "KMH": "Kilometer pro Stunde",
            "D64": "Kalendertage",
            "DZN": "Dutzend",
            "KT": "Bausatz (kit)"
        }
    },
    "idempotency": "POST /v1/generate: optionaler Header \"Idempotency-Key: <eigene ID>\" -- ein Retry mit demselben Schlüssel und demselben Body liefert die gespeicherte Antwort erneut zurück (Header X-Idempotent-Replay: true), ohne die Rechnung ein zweites Mal anzulegen oder Credits erneut zu ziehen. 24h gültig; derselbe Schlüssel mit anderem Body -> HTTP 409.",
    "endpoints": {
        "POST /v1/cii": "JSON-Rechnung -> EN-16931 CII-XML (1 Credit); invoice.profile=\"xrechnung\" -> XRechnung-XML (B2G)",
        "POST /v1/generate": "JSON-Rechnung -> ZUGFeRD-PDF/A-3 (1 Credit), inkl. GiroCode + Jetzt-zahlen-Zahlseite (URL im Header X-Pay-Url, pro Profil abschaltbar); Rechnung erscheint in der Konto-Historie (Mahnwesen). Optional invoice.payee.name = abweichender Zahlungsempfänger (BG-10). Mit \"seal\":true zusätzlich Echtheits-Siegel: QR wird ins PDF eingebettet, verify_url im Header X-Seal-Verify-Url (+2 Credits, Kunden-Key). Auch als Content-Type: application/xml mit einer fertigen EN-16931-Rechnung im Body, als CII (ZUGFeRD/Factur-X/XRechnung) oder als XRechnung-UBL (Invoice/CreditNote; aus UBL wird bei uns CII); Optionen dann als ?seal=1&template=1, Hinweise zum Import im Header X-Import-Warnings. Fremd-XML ohne EN-16931-Syntax -> 422, dafuer /v1/import?ai=1. Mit ?validate=1 wird vor der Auslieferung gegen KoSIT geprüft; ist der Validator nicht verfügbar, kommt die Rechnung trotzdem, mit Header X-Validation: unavailable. Siehe \"idempotency\" oben.",
        "POST /v1/embed": "Eigenes PDF (PDF/A-1b) + JSON -> Factur-X-PDF/A-3, Layout bleibt (2 Credit). Statt \"invoice\" auch \"invoice_xml\" (EN-16931-XML als Text, CII oder UBL) bzw. multipart-Feld \"xml\" (Datei). Mit \"seal\":true zusätzlich Echtheits-Siegel: verify_url im Header X-Seal-Verify-Url (Layout bleibt unverändert, QR selbst platzieren; +2 Credits, Kunden-Key)",
        "POST /v1/import": "Import aus Excel (.xlsx/.xls), LibreOffice (.ods), CSV oder EN-16931-XML (CII oder XRechnung-UBL): multipart-Feld \"file\" (Endung im Dateinamen) oder Datei als roher Body. ?mode=preview (Standard, kostenlos): erkannte Rechnungen als JSON mit Summen und Fehlern je Rechnung; Tabellen = eine Zeile je Position (Spalten siehe /docs), eine einzelne Excel-Rechnung wird als Ganzes gelesen (needs_ai:true, wenn die Struktur nicht erkennbar ist oder ein XML weder CII noch UBL ist, Antwort dann mit kind:fremd; dann ?ai=1 fuer die KI-Zuordnung, Beta, 10 Credits). ?mode=generate: je Rechnung ZUGFeRD-PDF/A-3 (1 Credit je Rechnung; bei .xlsx/.xls/.ods zusaetzlich 2 Credits Umwandlung je Datei; ?template=1 eigene Word-Vorlage, ?seal=1 Echtheitssiegel, Kosten vorab in costs der Vorschau, Hinweise im Header X-Import-Notes), Antwort ZIP, bei genau einer Rechnung direkt das PDF (Header X-Invoice-Number); bei Validierungsfehlern 422 mit Liste und ohne Erzeugung; ab 51 Rechnungen 202 mit job_id (Hintergrundlauf). Spalte absender (Kürzel, USt-IdNr., Steuernummer oder voller Name einer eigenen Firma): ein Aufruf erzeugt nur in der Firma des Schlüssels bzw. von X-Company; eine andere eigene Firma oder ein unbekannter Absender ist ein Fehler der Rechnung („Zeile N: …“), ebenso Zeilen ohne Absender vor der ersten Zeile mit Absender derselben Rechnungsnummer. Kunden-Key.",
        "GET /v1/import/{id}": "Stand eines Hintergrundlaufs: status queued|running|done|failed, total/done/failed, errors. Kostenlos, auch mit nur-lesendem Key.",
        "GET /v1/import/{id}/zip": "Ergebnis-ZIP eines fertigen Laufs (7 Tage aufbewahrt). Kostenlos.",
        "POST /v1/validate": "XML/PDF -> KoSIT-Prüfung (1 Credit). Auch eine nicht konforme Rechnung antwortet mit HTTP 200; das Urteil steht im Feld \"valid\", die Verstöße in \"messages\" -- werten Sie \"valid\" aus, nicht den Statuscode. HTTP 503 nur bei nicht laufendem Validator (kostenlos).",
        "POST /v1/extract": "PDF -> eingebettetes XML + Felder (1 Credit)",
        "POST /v1/validate/fraud": "PDF/XML -> KoSIT + IBAN-Betrugsabgleich (3 Credits, Kunden-Key)",
        "POST /v1/check": "PDF/XML -> Eingangsprüfung: KoSIT + IBAN-Historie + Dublette + Betrugsmuster (Zahlungsdruck, Betragsplausibilität, Doppelrechnung mit neuer Nummer, PDF-Forensik/Metadaten) + externe Quellen (VIES, Bundesbank-BLZ, EU-Sanktionsliste, Anschrift) -> Risk-Report (grün/gelb/rot), in Historie gespeichert (2 Credits, Kunden-Key)",
        "POST /v1/seal": "Rechnung (JSON wie /generate ODER Kernfelder) -> Echtheits-Siegel: Ed25519-signiert, gibt verify_url zurück; Empfänger prüft öffentlich unter GET /verify/{token} (2 Credits, Kunden-Key)",
        "GET /verify/{token}": "Öffentliche Verifikation eines Siegels (kein Auth, kostenlos): zeigt Aussteller, Nr., Betrag, IBAN + „echt/unverändert\"",
        "POST /v1/diff": "original + corrected -> Positions-Diff (JSON | ?format=html) (1 Credit)",
        "POST /v1/automap": "KI-Import: unstrukturierter Text/Excel-Zeile (roh im Body oder {\"data\": \"...\"}) -> erkanntes Rechnungs-JSON (mapped + normalized) im vollen Rechnungsschema (Referenzen, Leitweg-ID, Zeitraum, Skonto, Nachlass/Zuschlag, Zahlungsempfaenger, Lieferanschrift, Positionsdetails), ergaenzt um Profil/Kundenstamm/Katalog. Mit ?generate=1 bzw. {\"generate\":true} direkt das fertige ZUGFeRD-PDF (dann zusaetzlich der Generate-Tarif). (10 Credits, Kunden-Key)",
        "POST /v1/dunning": "Mahnschreiben-PDF zu eigener Rechnung, zählt die Mahnstufe hoch. Body {number, level?} (1 Credit, Kunden-Key)",
        "GET /v1/receivables": "Accounts receivable as JSON (amounts in cents). Query: status=open|overdue|paid|all, since=<ISO>, page, per_page; number=<n> returns one item incl. payments[]. Empty list is HTTP 200. (free, customer key)",
        "POST /v1/receivables/payment": "Book an incoming payment. Body {number, amount_cents} (partial or full, free, customer key)",
        "POST /v1/receivables/paid": "Mark an invoice fully paid. Body {number} (free, customer key)",
        "POST /v1/receivables/refund": "Book the refund of an overpayment. Body {number, amount_cents, value_date?, note?}. Not yet available, answers 501 (free, customer key)",
        "GET /v1/partners": "Business partner master data as JSON. Query: q=<search>, role=customer|supplier|prospect, country=<ISO2>, archived=1, page, per_page; no=<partner_no> returns one partner (incl. archived ones). Empty list is HTTP 200. (free, customer key)",
        "POST /v1/partners": "Create or update a partner, keyed by its partner number. Body {partner_no, name, street, zip, city, country, vat_id, tax_number, email, phone, is_customer, is_supplier, is_prospect, legal_status, sepa_mandate, sepa_iban, payment_terms_days, skonto_days, skonto_percent, tax_scheme_override, currency, invoice_profile, leitweg_id, billing_*, credit_limit_cents, dunning_mode (auto|manual|off), dunning_tone (freundlich|neutral|bestimmt), dunning_fees {\"2\": cents, \"3\": cents}, dunning_pauschale, dunning_damages, dunning_stage_days {\"1\": days, \"2\": days, \"3\": days}, dunning_deadline_days, dunning_texts {\"1\"..\"3\": text}, dunning_texts_en {\"1\"..\"3\": English text, empty = English default}; dunning_* null = inherit from account}. Omitted fields keep their stored value; 201 on create, 200 on update; invalid dunning values -> 422. (free, customer key)",
        "POST /v1/partners/archive": "Archive a partner (never deleted -- records stay, GoBD). Body {no} (free, customer key)",
        "POST /v1/embed/token": "White-Label: Embed-Token minten (kostenlos, nur POST, Kunden-Key mit vollem Zugriff, ein nur lesender Key erhält 403). Body {ttl?, accent?, title?}: ttl in Sekunden, mindestens 60, höchstens 7 Tage (604800), Standard 24 Stunden; expires_in nennt die wirksame Laufzeit",
        "GET /v1/companies": "Firmen der Firmengruppe als JSON (id, code, name, country, vat_id), die dieser Key per X-Company ansprechen darf; ohne Gruppenrecht nur die eigene. Kostenlos, auch mit nur-lesendem Key."
    }
}