{"openapi":"3.0.3","info":{"title":"Claribill API v1","version":"1.0.0","description":"Öffentliche REST-API von Claribill für externe Integrationen: Steuerberater-Software, Buchhaltungssysteme, Shops, CRMs und eigene Anwendungen.\n\nInteraktive Referenz: [https://www.claribill.com/api-reference](https://www.claribill.com/api-reference) · Benutzer-Dokumentation: [https://www.claribill.com/docs](https://www.claribill.com/docs)\n\n## Authentifizierung\nJede Anfrage trägt einen API-Schlüssel im Header `Authorization: Bearer sk_live_...`. Schlüssel erstellen: Einstellungen → API-Schlüssel (/dashboard/settings/api-keys), nur für Inhaber und Admin. Jeder Schlüssel gehört zu genau einer Firma und trägt Scopes; alle Daten sind auf diese Firma beschränkt.\n\n## Pakete und Kontingent\nAPI-Zugang ist in Professional (50.000 Aufrufe/Monat) und Enterprise (250.000 Aufrufe/Monat) enthalten. Free und Starter haben keinen API-Zugang.\n\n## Rate-Limit\n100 Anfragen pro Minute pro API-Key.\n\n## Antwortformat und Pagination\nAlle v1-Endpunkte, die Daten liefern, verpacken die Nutzlast in { data: … }. Listen-Endpunkte liefern zusätzlich { data: [...], pagination: { total, limit, offset, hasMore } } (bzw. meta bei einigen Ressourcen). Erfolgreiche Aktionen ohne Rückgabeobjekt antworten { success: true }. Ausnahme sind die Blog-Routen unter /api/v1/blog/** (nur Plattform-Admins): Sie liefern Listen unter einem eigenen Schlüssel ({ posts }, { categories }, { tags }, { media }), Einzelobjekte ohne Hülle, KI-Jobs als { job } und Löschungen als { ok: true }.\n\nListen-Endpunkte akzeptieren `limit` (Standard 50, höchstens 100) und `offset`.\n\n## Fehler\nFehler haben die Form `{ error: string, code?: string, allowedValues?: string[] }`.\n\n- **400** – Ungültige Eingabe (Pflichtfeld, Enum-Wert, Zahlen-/Datumsformat, Geschäftsregel).\n- **401** – Authorization-Header fehlt, ist kein Bearer-Header, oder der Schlüssel ist ungültig/deaktiviert.\n- **403** – Scope fehlt (requiredScopes in der Antwort), Plan ohne API-Zugang (code: \"plan_no_access\") oder ein Plan-Limit ist erschöpft.\n- **404** – Datensatz existiert nicht oder gehört zu einer anderen Firma — es werden nie Daten fremder Firmen ausgeliefert.\n- **409** – Zustandskonflikt: falscher Status für die Aktion, gleichzeitige Änderung, bereits vergebene Nummer/Referenz.\n- **429** – Monatskontingent erschöpft (code: \"quota_exceeded\") oder Rate-Limit erreicht.\n- **500** – Unerwarteter interner Fehler.\n- **502** – Ein abhängiger externer Dienst ist fehlgeschlagen (z. B. E-Mail-Versand, S3).\n\n## Feldnamen\nRequest-Bodies akzeptieren praktisch überall sowohl camelCase als auch snake_case für dasselbe Feld (z. B. customerNumber = customer_number). Neuere Ressourcen antworten durchgehend camelCase; die vier oben genannten Bestandsressourcen bleiben aus Kompatibilitätsgründen bei ihrer ursprünglichen snake_case-Antwortform, ergänzt um camelCase-Aliase für neue Felder.","termsOfService":"https://www.claribill.com/agb","contact":{"name":"Claribill Support","url":"https://www.claribill.com/support"}},"externalDocs":{"description":"Claribill-Dokumentation","url":"https://www.claribill.com/docs"},"servers":[{"url":"https://www.claribill.com","description":"Produktion"}],"tags":[{"name":"invoices","description":"Rechnungen – Rechnungen und Proformas inkl. Zahlungen, Mahnwesen, Berichtigung, Versionierung und Factur-X/ZUGFeRD-Export."},{"name":"credit-notes","description":"Gutschriften – Freie und mit einer Rechnung verknüpfte Gutschriften sowie deren Auszahlung. Gutschriften sind Invoice-Zeilen und teilen sich die invoices:read/invoices:write-Scopes."},{"name":"customers","description":"Kunden – Kundenstammdaten inkl. Adressen, Bankdaten, SEPA-Mandat, UID-Validierung über VIES und Duplikatsuche."},{"name":"products","description":"Produkte – Produkt- und Leistungskatalog für die Vorbelegung von Positionen."},{"name":"quotes","description":"Angebote – Angebote inkl. Versand, Statuswechsel und Umwandlung in Rechnung oder Auftragsbestätigung."},{"name":"order-confirmations","description":"Auftragsbestätigungen – Auftragsbestätigungen inkl. Versand, Statuswechsel und Umwandlung in Rechnung oder Auftrag."},{"name":"delivery-notes","description":"Lieferscheine – Lieferscheine ohne Preisangaben inkl. Versand und Umwandlung in eine Rechnung mit nachträglicher Preisangabe."},{"name":"orders","description":"Aufträge – Langlaufende Aufträge mit Budget, Teillieferungen und Teilrechnungen."},{"name":"recurring-schedules","description":"Wiederkehrende Rechnungen – Zeitpläne für automatisch generierte Rechnungen (Abos, Wartungsverträge, Mieten)."},{"name":"documents","description":"Belege (Dokumentenablage) – Zentrale Dokumentenablage für beliebige Belege inkl. Direkt-Upload über S3."},{"name":"exports","description":"Exporte – Buchhaltungsexporte (CSV, DATEV, BMD, UStVA) und SEPA-Lastschriftdateien."},{"name":"company","description":"Firmendaten – Firmenstammdaten, Nummernkreise, Plan-Funktionen und Verbrauch — nur lesend."},{"name":"webhooks","description":"Webhooks – Webhook-Endpunkte für Ereignisbenachrichtigungen (nur Enterprise-Plan)."},{"name":"blog","description":"Blog (nur Plattform-Admins) – Internes CMS für den Claribill-Blog. Eigene Authentifizierung über lib/blog/api-auth.ts: der API-Key muss zusätzlich einem Plattform-Admin gehören. Kein Kunden-Feature — hier nur mit Methode, Pfad und Scope gelistet."},{"name":"meta","description":"Meta & Discovery – Maschinenlesbare Beschreibung der API selbst."}],"paths":{"/api/v1/invoices":{"get":{"operationId":"get_invoices","tags":["invoices"],"summary":"Rechnungen auflisten (inkl. Gutschriften und Proformas)","description":"**Scope:** `invoices:read`\n\n**Antwort:** { data: Invoice[], pagination }. Ohne invoice_type sind auch Gutschriften und Proformas enthalten. Sortierung issueDate absteigend.\n\n- Alle Abfragen sind auf die Firma des API-Keys gefiltert.\n- Ungültiger status/invoice_type → 400 mit allowedValues.","parameters":[{"name":"status","in":"query","required":false,"description":"Filtert nach Status, Groß-/Kleinschreibung egal.","schema":{"type":"string","enum":["DRAFT","FINAL","SENT","PAID","OVERDUE","CANCELLED"]}},{"name":"invoice_type","in":"query","required":false,"description":"Filtert nach Belegart. Alias: `invoiceType`.","schema":{"type":"string","enum":["INVOICE","CREDIT_NOTE","PROFORMA"]}},{"name":"customer_id","in":"query","required":false,"description":"Filtert nach Kunde. Alias: `customerId`.","schema":{"type":"string"}},{"name":"search","in":"query","required":false,"description":"Case-insensitive Teiltreffer in Belegnummer oder Kundenname.","schema":{"type":"string"}},{"name":"from","in":"query","required":false,"description":"issueDate ≥ from.","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","required":false,"description":"issueDate ≤ to.","schema":{"type":"string","format":"date"}},{"name":"category_id","in":"query","required":false,"description":"Filtert nach Dokumentkategorie.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Seitengröße.","schema":{"type":"number","default":50}},{"name":"offset","in":"query","required":false,"description":"Versatz.","schema":{"type":"number","default":0}}],"responses":{"200":{"description":"{ data: Invoice[], pagination }. Ohne invoice_type sind auch Gutschriften und Proformas enthalten. Sortierung issueDate absteigend.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Invoice"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:read"]},"post":{"operationId":"post_invoices","tags":["invoices"],"summary":"Rechnung (oder Proforma) als Entwurf anlegen","description":"**Scope:** `invoices:write`\n\n**Antwort:** 201 { data: Invoice }, wie GET-Detail ohne payments.\n\n- Legt einen Entwurf mit Platzhalternummer ENTWURF-… an; die echte Nummer vergibt erst der Statuswechsel auf FINAL/SENT bzw. POST …/send.\n- Summen über prepareInvoiceTaxPayload — identisch zum Dashboard: absoluter Positionsrabatt, Normalisierung der Steuersätze auf das Steuerland, automatische OSS-Ableitung, sofern kein Kennzeichen mitgeschickt wird.\n- 400 bei Pflichtfeld/Enum/Rabatt über dem Positionsbetrag (lineIndex in der Antwort); 404 Customer/Company/Document category not found.\n- Audit INVOICE_CREATED_VIA_API. Webhook invoice.created.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"customerId":{"type":"string","description":"Muss zur Firma gehören."},"lines":{"type":"array","items":{"type":"object","properties":{"description":{"type":"string","description":"Positionsbeschreibung."},"quantity":{"type":"number","default":1,"description":"Menge."},"unitPrice":{"type":"number","default":0,"description":"Einzelpreis netto. Alias: `unit_price`."},"taxRate":{"type":"number","description":"Steuersatz in %. Alias: `tax_rate`. Standard: Standardsatz des Steuerlands."},"discount":{"type":"number","default":0,"description":"Absoluter Rabattbetrag (kein Prozentsatz!)"},"lineType":{"type":"string","enum":["PRODUCT","SERVICE","DISCOUNT_PERCENT","DISCOUNT_ABSOLUTE","SHIPPING","ADVANCE_PAYMENT","CREDIT","TEXT","SUBTOTAL","PAGEBREAK"],"default":"PRODUCT","description":"Positionstyp. Alias: `line_type`."}}},"description":"Mindestens eine Position."},"invoiceType":{"type":"string","enum":["INVOICE","PROFORMA"],"default":"INVOICE","description":"CREDIT_NOTE → 400, Verweis auf POST /api/v1/credit-notes. Alias: `invoice_type`."},"issueDate":{"type":"string","format":"date","description":"Rechnungsdatum. Standard: heute."},"dueDate":{"type":"string","format":"date","description":"Fälligkeit. Standard: customer.paymentTermDays ?? company.paymentTermDays ?? 14 Tage."},"servicePeriodStart":{"type":"string","format":"date","nullable":true,"description":"Leistungszeitraum."},"servicePeriodEnd":{"type":"string","format":"date","nullable":true,"description":"Leistungszeitraum."},"currency":{"type":"string","default":"EUR","description":"Währungscode."},"reverseCharge":{"type":"boolean","default":false,"description":"Steuerschuldnerschaft des Leistungsempfängers."},"taxExemptionReason":{"type":"string","enum":["NONE","SMALL_BUSINESS","REVERSE_CHARGE_EU","REVERSE_CHARGE_EU_NO_VAT","THIRD_COUNTRY","INTRA_COMMUNITY","EXPORT_DELIVERY","REVERSE_CHARGE_13B","INTERNAL_SUPPLY","OTHER_EXEMPTION"],"description":"Ungültig → 400."},"taxExemptionNote":{"type":"string","description":"Freitext zur Befreiung."},"taxCountry":{"type":"string","description":"Steuer- bzw. Lieferland."},"deliveryCountry":{"type":"string","description":"Steuer- bzw. Lieferland."},"ossApplicable":{"type":"boolean","description":"Ohne Angabe wird OSS aus dem Steuerkontext abgeleitet. Alias: `oss_applicable`."},"ossCountry":{"type":"string","description":"OSS-Bestimmungsland. Alias: `oss_country`."},"notes":{"type":"string","description":"Interne Anmerkung, nicht gedruckt."},"footer":{"type":"string","description":"Schlusstext (gedruckt)"},"headerText":{"type":"string","description":"Einleitungstext (gedruckt) Alias: `header_text`."},"customTitle":{"type":"string","description":"Eigener Belegtitel statt „Rechnung“. Alias: `custom_title`."},"pdfWithoutLetterhead":{"type":"boolean","default":false,"description":"PDF ohne Briefkopf. Alias: `pdf_without_letterhead`."},"invoiceDateIsServiceDate":{"type":"boolean","default":false,"description":"Rechnungsdatum = Leistungsdatum. Alias: `invoice_date_is_service_date`."},"useShippingAddress":{"type":"boolean","default":false,"description":"Aktiviert shippingName/-Street/-ZipCode/-City/-Country. Alias: `use_shipping_address`."},"documentCategoryId":{"type":"string","nullable":true,"description":"Dokumentkategorie. Alias: `document_category_id`."},"orderNumber":{"type":"string","description":"Bestellnummer/Auftragsreferenz. Alias: `order_number`."},"buyerReference":{"type":"string","description":"Käuferreferenz bzw. Leitweg-ID. Alias: `buyer_reference`."},"eInvoiceAudit":{"type":"object","description":"Auditdaten für Format, Einreichkanal, Portal-URL, Status."},"e_invoice":{"type":"object","description":"Auditdaten für Format, Einreichkanal, Portal-URL, Status."},"metadata":{"type":"object","description":"Freies JSON-Objekt."}},"required":["customerId","lines"]}}}},"responses":{"201":{"description":"201 { data: Invoice }, wie GET-Detail ohne payments.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Invoice"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:write"]}},"/api/v1/invoices/{id}":{"get":{"operationId":"get_invoices_id","tags":["invoices"],"summary":"Rechnungsdetails lesen","description":"**Scope:** `invoices:read`\n\n**Antwort:** { data: Invoice } inkl. footer, sentAt, paidAt, lockedAt, customer (mit Adresse/UID), lines[], payments[], paidAmount.\n\n- 404 Invoice not found, auch für fremde Firmen.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"{ data: Invoice } inkl. footer, sentAt, paidAt, lockedAt, customer (mit Adresse/UID), lines[], payments[], paidAmount.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Invoice"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:read"]},"put":{"operationId":"put_invoices_id","tags":["invoices"],"summary":"Rechnung ändern und/oder Status wechseln","description":"**Scope:** `invoices:write`\n\n**Antwort:** 200 { data: { id, invoiceNumber, status, netTotal, taxTotal, grossTotal, orderNumber, buyerReference, e_invoice, updatedAt, [stornoInvoiceId, stornoInvoiceNumber] } }\n\n- Verlässt der Beleg dabei den Entwurf, zählt er aufs Dokumentenkontingent des Plans: bei erschöpftem Kontingent 403 plan_limit_reached (Stornieren eines Entwurfs bleibt möglich).\n- Statuswechsel über lib/invoice-status-transitions.ts: DRAFT→FINAL|SENT|CANCELLED, FINAL→SENT|DRAFT|CANCELLED, SENT→PAID|OVERDUE|CANCELLED, OVERDUE→PAID|CANCELLED; PAID/CANCELLED sind Endzustände. Ungültig → 400 mit allowedTransitions.\n- DRAFT→FINAL/SENT vergibt die echte Nummer atomar (Nummernkreis nach Belegart, keine Lücken); →SENT setzt sentAt und schreibt fest (lockedAt, v1-Snapshot); FINAL bleibt bewusst ungesperrt; →PAID setzt paidAt.\n- →CANCELLED erzeugt für ausgestellte Rechnungen automatisch die Storno-Gutschrift (wie POST …/cancel mit createStorno: true), ohne E-Mail.\n- Inhaltliche Änderungen nur an bearbeitbaren Rechnungen (DRAFT oder FINAL und nicht festgeschrieben, lib/invoice-lock.ts) — sonst 400 mit lockedFields und Verweis auf die Berichtigung. Ein unveränderter Read-Modify-Write-Body stört den Statuswechsel nicht.\n- Webhooks invoice.sent/invoice.paid/invoice.cancelled beim Statuswechsel, sonst invoice.updated; zusätzlich credit_note.created bei automatischem Storno. Audit INVOICE_UPDATED_VIA_API. Konflikte → 409.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["DRAFT","FINAL","SENT","PAID","OVERDUE","CANCELLED"],"description":"Statuswechsel, ungültig → 400 mit allowedValues."},"documentCategoryId":{"description":"Alle optional, jeweils auch snake_case; nur Inhaltsfelder — siehe Hinweise für die Sperre."},"dueDate":{"description":"Alle optional, jeweils auch snake_case; nur Inhaltsfelder — siehe Hinweise für die Sperre."},"servicePeriodStart":{"description":"Alle optional, jeweils auch snake_case; nur Inhaltsfelder — siehe Hinweise für die Sperre."},"servicePeriodEnd":{"description":"Alle optional, jeweils auch snake_case; nur Inhaltsfelder — siehe Hinweise für die Sperre."},"reverseCharge":{"description":"Alle optional, jeweils auch snake_case; nur Inhaltsfelder — siehe Hinweise für die Sperre."},"taxExemptionReason":{"description":"Alle optional, jeweils auch snake_case; nur Inhaltsfelder — siehe Hinweise für die Sperre."},"taxExemptionNote":{"description":"Alle optional, jeweils auch snake_case; nur Inhaltsfelder — siehe Hinweise für die Sperre."},"taxCountry":{"description":"Alle optional, jeweils auch snake_case; nur Inhaltsfelder — siehe Hinweise für die Sperre."},"deliveryCountry":{"description":"Alle optional, jeweils auch snake_case; nur Inhaltsfelder — siehe Hinweise für die Sperre."},"ossApplicable":{"description":"Alle optional, jeweils auch snake_case; nur Inhaltsfelder — siehe Hinweise für die Sperre."},"ossCountry":{"description":"Alle optional, jeweils auch snake_case; nur Inhaltsfelder — siehe Hinweise für die Sperre."},"headerText":{"description":"Alle optional, jeweils auch snake_case; nur Inhaltsfelder — siehe Hinweise für die Sperre."},"notes":{"description":"Alle optional, jeweils auch snake_case; nur Inhaltsfelder — siehe Hinweise für die Sperre."},"footer":{"description":"Alle optional, jeweils auch snake_case; nur Inhaltsfelder — siehe Hinweise für die Sperre."},"orderNumber":{"description":"Alle optional, jeweils auch snake_case; nur Inhaltsfelder — siehe Hinweise für die Sperre."},"buyerReference":{"description":"Alle optional, jeweils auch snake_case; nur Inhaltsfelder — siehe Hinweise für die Sperre."},"customTitle":{"description":"Alle optional, jeweils auch snake_case; nur Inhaltsfelder — siehe Hinweise für die Sperre."},"pdfWithoutLetterhead":{"description":"Alle optional, jeweils auch snake_case; nur Inhaltsfelder — siehe Hinweise für die Sperre."},"invoiceDateIsServiceDate":{"description":"Alle optional, jeweils auch snake_case; nur Inhaltsfelder — siehe Hinweise für die Sperre."},"useShippingAddress":{"description":"Alle optional, jeweils auch snake_case; nur Inhaltsfelder — siehe Hinweise für die Sperre."},"eInvoiceAudit":{"description":"Alle optional, jeweils auch snake_case; nur Inhaltsfelder — siehe Hinweise für die Sperre."},"e_invoice":{"description":"Alle optional, jeweils auch snake_case; nur Inhaltsfelder — siehe Hinweise für die Sperre."},"lines":{"type":"array","items":{"type":"object"},"description":"Ersetzt alle Positionen (mind. 1)"}}}}}},"responses":{"200":{"description":"200 { data: { id, invoiceNumber, status, netTotal, taxTotal, grossTotal, orderNumber, buyerReference, e_invoice, updatedAt, [stornoInvoiceId, stornoInvoiceNumber] } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:write"]},"delete":{"operationId":"delete_invoices_id","tags":["invoices"],"summary":"Rechnungsentwurf löschen","description":"**Scope:** `invoices:write`\n\n**Antwort:** 200 { success: true }\n\n- Nur Entwürfe (DRAFT), sonst 400. Löscht Positionen und Beleg in einer Transaktion und gibt verknüpfte Ursprungsbelege (Angebot, AB, Lieferschein) wieder frei.\n- Audit INVOICE_DELETED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"200 { success: true }","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:write"]}},"/api/v1/invoices/{id}/pdf":{"get":{"operationId":"get_invoices_id_pdf","tags":["invoices"],"summary":"Rechnungs-PDF laden","description":"**Scope:** `invoices:read`\n\n**Antwort:** 200 PDF-Binary, Content-Type application/pdf, Content-Disposition inline, Header X-PDF-Source: generated|s3-cache.\n\n- Rendert über lib/invoice-pdf-bundle.ts (gleiches Layout wie das Dashboard), bewusst ohne S3-Cache.\n- 404 Invoice not found. Audit PDF_DOWNLOADED_VIA_API nur bei Treffer.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"200 PDF-Binary, Content-Type application/pdf, Content-Disposition inline, Header X-PDF-Source: generated|s3-cache.","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:read"]}},"/api/v1/invoices/{id}/send":{"post":{"operationId":"post_invoices_id_send","tags":["invoices"],"summary":"Rechnung per E-Mail versenden","description":"**Scope:** `invoices:write`\n\n**Antwort:** 200 { data: { invoiceId, invoiceNumber, status (klein geschrieben), sentTo, cc, bcc, sentAt } } (jeweils auch snake_case)\n\n- Versand eines Entwurfs bei erschöpftem Dokumentenkontingent → 403 plan_limit_reached (vor der Nummernvergabe).\n- Fachkern lib/invoice-api-email.ts. Entwürfe mit Platzhalter bekommen vorher die echte Nummer. PDF + E-Mail-Text wie im Dashboard.\n- Erst nach erfolgreichem Versand: Status DRAFT/FINAL → SENT, sentAt, Festschreibung (lockedAt, v1). Audit INVOICE_SENT_VIA_API, Webhook invoice.sent.\n- Eine stornierte Rechnung lässt sich versenden und bleibt storniert: Vermerkt werden sentAt (falls noch leer), Audit und Webhook invoice.sent mit dem tatsächlichen Status.\n- Fehler: 404 unbekannt, 400 keine Empfängeradresse oder nie ausgestellter Entwurf mit Platzhalternummer, 409 umgewandelte Proforma, 500 Versand fehlgeschlagen.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"recipientEmail":{"type":"string","description":"Empfänger. Alias: `recipient_email`. Standard: E-Mail des Kunden."},"cc":{"type":"string","description":"Kopie."},"bcc":{"type":"string","description":"Blindkopie."}}}}}},"responses":{"200":{"description":"200 { data: { invoiceId, invoiceNumber, status (klein geschrieben), sentTo, cc, bcc, sentAt } } (jeweils auch snake_case)","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:write"]}},"/api/v1/invoices/{id}/cancel":{"post":{"operationId":"post_invoices_id_cancel","tags":["invoices"],"summary":"Rechnung stornieren (mit optionaler Storno-Gutschrift)","description":"**Scope:** `invoices:write`\n\n**Antwort:** 200 { data: { id, invoiceNumber, status: \"CANCELLED\", stornoInvoiceId, stornoInvoiceNumber, stornoEmailSent, [warning] } }\n\n- Fachkern cancelInvoiceWithOptionalStorno (lib/storno.ts). Ein Gegenbeleg entsteht nur für ausgestellte Rechnungen vom Typ INVOICE (isStornoEligible): Gutschrift Grund CANCELLATION, eigene GS-Nummer, Status FINAL, negative Beträge — atomar mit dem Statuswechsel.\n- Entwürfe/Proformas/Gutschriften werden nur auf CANCELLED gesetzt. Stornierbar aus DRAFT, FINAL, SENT, OVERDUE; sonst 409. Paralleler Statuswechsel → 409.\n- Optionaler E-Mail-Versand ist nicht fatal (→ warning), Storno bleibt bestehen. Webhooks invoice.cancelled und credit_note.created (+ invoice.sent bei Versand).\n- Audit INVOICE_CANCELLED_VIA_API (Urheber: ältester OWNER der Firma).","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"createStorno":{"type":"boolean","default":true,"description":"Storno-Gutschrift erzeugen. Alias: `create_storno`."},"sendStornoToCustomer":{"type":"boolean","default":false,"description":"Storno-Gutschrift per E-Mail versenden. Alias: `send_storno_to_customer`."},"recipientEmail":{"type":"string","description":"Nur mit Versand relevant. Alias: `recipient_email`."}}}}}},"responses":{"200":{"description":"200 { data: { id, invoiceNumber, status: \"CANCELLED\", stornoInvoiceId, stornoInvoiceNumber, stornoEmailSent, [warning] } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:write"]}},"/api/v1/invoices/{id}/storno":{"post":{"operationId":"post_invoices_id_storno","tags":["invoices"],"summary":"Nachträgliche Storno-Gutschrift zu einer bereits stornierten Rechnung","description":"**Scope:** `invoices:write`\n\n**Antwort:** 201 { data: { id, invoiceNumber, invoiceType: \"CREDIT_NOTE\", creditNoteReason: \"CANCELLATION\", status: \"FINAL\", parentInvoiceId, issueDate, currency, netTotal, taxTotal, grossTotal, stornoEmailSent, [warning] } }\n\n- Spiegelt createRetroactiveStorno für Rechnungen, die bereits CANCELLED sind, aber noch keine Storno-Gutschrift haben. Kein Statuswechsel.\n- 404 unbekannt; 409 Rechnung nicht CANCELLED (Verweis auf …/cancel) oder Storno existiert bereits; 400 kein stornofähiger Beleg (z. B. Proforma/Entwurf).\n- Webhook credit_note.created (+ invoice.sent bei Versand). Audit INVOICE_STORNO_CREATED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"sendToCustomer":{"type":"boolean","default":false,"description":"Storno-Gutschrift versenden. Alias: `send_to_customer`."},"recipientEmail":{"type":"string","description":"Empfänger. Alias: `recipient_email`."}}}}}},"responses":{"201":{"description":"201 { data: { id, invoiceNumber, invoiceType: \"CREDIT_NOTE\", creditNoteReason: \"CANCELLATION\", status: \"FINAL\", parentInvoiceId, issueDate, currency, netTotal, taxTotal, grossTotal, stornoEmailSent, [warning] } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:write"]}},"/api/v1/invoices/{id}/duplicate":{"post":{"operationId":"post_invoices_id_duplicate","tags":["invoices"],"summary":"Rechnung als neuen Entwurf duplizieren","description":"**Scope:** `invoices:write`\n\n**Antwort:** 201 { data: { id, invoiceNumber, invoiceType: \"INVOICE\", status: \"DRAFT\", sourceInvoiceId, customerId, documentCategoryId, issueDate, dueDate, currency, netTotal, taxTotal, grossTotal, createdAt } }\n\n- Fachkern lib/invoice-duplicate.ts. Kopie ist immer eine reguläre Rechnung als Entwurf mit Platzhalternummer; Mengen/Summen werden positiv übernommen (auch bei Kopie einer Gutschrift), Leistungszeitraum wird nicht kopiert.\n- 404 Invoice/Customer not found; 403 plan_limit_reached, wenn das Dokumentenkontingent des Plans erreicht ist (die Kopie zählt wie ein neuer Beleg). Audit INVOICE_DUPLICATED_VIA_API. Kein Webhook (wie im Dashboard).","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"customerId":{"type":"string","description":"Anderer Kunde als das Original. Alias: `customer_id`."},"adjustDates":{"type":"boolean","default":true,"description":"Rechnungsdatum = heute, Zahlungsziel bleibt in Tagen gleich. Alias: `adjust_dates`."}}}}}},"responses":{"201":{"description":"201 { data: { id, invoiceNumber, invoiceType: \"INVOICE\", status: \"DRAFT\", sourceInvoiceId, customerId, documentCategoryId, issueDate, dueDate, currency, netTotal, taxTotal, grossTotal, createdAt } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:write"]}},"/api/v1/invoices/{id}/convert":{"post":{"operationId":"post_invoices_id_convert","tags":["invoices"],"summary":"Proforma in eine reguläre Rechnung umwandeln","description":"**Scope:** `invoices:write`\n\n**Antwort:** 201 { data: { id, invoiceNumber, invoiceType: \"INVOICE\", status: \"DRAFT\", proformaInvoiceId, proformaInvoiceNumber, customerId, documentCategoryId, issueDate, dueDate, currency, netTotal, taxTotal, grossTotal, createdAt } }\n\n- Kein Body. Fachkern convertProformaToInvoiceCore (lib/proforma-conversion.ts). Die Proforma behält Nummer/Belegart/Status und wird über metadata.convertedToInvoiceId verknüpft (umgekehrt metadata.convertedFromProformaId).\n- Einleitungs-/Abschlusstext kommen aus den Rechnungsvorlagen der Firma, nicht von der Proforma. Umwandelbar aus DRAFT, FINAL, SENT, OVERDUE.\n- 404 unbekannt; 400 kein Proforma-Beleg; 403 plan_limit_reached bei erreichtem Dokumentenkontingent; 409 bereits umgewandelt (nennt die Rechnung) oder Status nicht mehr umwandelbar.\n- Audit CONVERT_TO_INVOICE (an der Proforma) und CREATE_FROM_PROFORMA (an der Rechnung). Webhook invoice.created.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"201":{"description":"201 { data: { id, invoiceNumber, invoiceType: \"INVOICE\", status: \"DRAFT\", proformaInvoiceId, proformaInvoiceNumber, customerId, documentCategoryId, issueDate, dueDate, currency, netTotal, taxTotal, grossTotal, createdAt } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:write"]}},"/api/v1/invoices/{id}/payments":{"get":{"operationId":"get_invoices_id_payments","tags":["invoices"],"summary":"Zahlungen einer Rechnung auflisten","description":"**Scope:** `invoices:read`\n\n**Antwort:** { data: { invoice_id, invoice_number, gross_total, total_paid, amount_due, currency, payments: [{ id, amount, payment_date/paymentDate, payment_method/paymentMethod, reference, notes, created_at/createdAt }] } }, Zahlungen nach date absteigend.\n\n- 404 Invoice not found für fremde/unbekannte Rechnung.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"{ data: { invoice_id, invoice_number, gross_total, total_paid, amount_due, currency, payments: [{ id, amount, payment_date/paymentDate, payment_method/paymentMethod, reference, notes, created_at/createdAt }] } }, Zahlungen nach date absteigend.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:read"]},"post":{"operationId":"post_invoices_id_payments","tags":["invoices"],"summary":"Zahlung zu einer Rechnung erfassen","description":"**Scope:** `invoices:write`\n\n**Antwort:** 201 { data: { …Zahlung, invoice_id/invoiceId, invoice_number/invoiceNumber, invoice_status/invoiceStatus (klein), total_paid/totalPaid, amount_due/amountDue } }\n\n- Rechnung wird bei vollständiger Zahlung auf PAID gesetzt inkl. paidAt (= Zahlungsdatum) und löst Webhook invoice.paid aus. Audit PAYMENT_RECORDED_VIA_API.\n- 400 amount fehlt/≤0; 400 ungültige payment_method (mit allowedValues); 400 ungültiges payment_date; 400 (code invoice_status) wenn Rechnung nicht FINAL/SENT/OVERDUE ist; 409 (code amount_exceeds_remaining) über offenem Rest + 0,01 €; 404 Rechnung fremd/unbekannt.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"amount":{"type":"number","description":"Betrag, muss > 0 sein."},"payment_method":{"type":"string","enum":["BANK_TRANSFER","CASH","CREDIT_CARD","PAYPAL","OTHER"],"default":"BANK_TRANSFER","description":"Groß-/Kleinschreibung und „-“ egal. Alias: `paymentMethod`."},"payment_date":{"type":"string","format":"date","description":"Zahlungsdatum. Alias: `paymentDate`. Standard: jetzt."},"reference":{"type":"string","description":"z. B. Transaktions-ID."},"notes":{"type":"string","description":"Freitext."}},"required":["amount"]}}}},"responses":{"201":{"description":"201 { data: { …Zahlung, invoice_id/invoiceId, invoice_number/invoiceNumber, invoice_status/invoiceStatus (klein), total_paid/totalPaid, amount_due/amountDue } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:write"]}},"/api/v1/invoices/{id}/payments/{paymentId}":{"put":{"operationId":"put_invoices_id_payments_paymentId","tags":["invoices"],"summary":"Zahlung ändern","description":"**Scope:** `invoices:write`\n\n**Antwort:** 200 { data: { …Zahlung, invoice_id/invoiceId, invoice_number/invoiceNumber, invoice_status/invoiceStatus, total_paid/totalPaid, amount_due/amountDue } }\n\n- Statusnachführung: vollständig bezahlt → PAID + paidAt + Webhook invoice.paid; wieder offen → zurück auf SENT, paidAt geleert. Audit PAYMENT_UPDATED_VIA_API.\n- 404 Payment not found (auch bei fremder Rechnung/Firma); 400 amount ≤0 / ungültige Zahlungsart / ungültiges Datum; 409 (code invoice_cancelled) bei stornierter Rechnung; 409 (code amount_exceeds_remaining) über dem offenen Rest ohne diese Zahlung.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}},{"name":"paymentId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"amount":{"type":"number","description":"> 0, Default = bisheriger Betrag."},"payment_method":{"type":"string","enum":["BANK_TRANSFER","CASH","CREDIT_CARD","PAYPAL","OTHER"],"description":"Wie POST. Alias: `paymentMethod`. Standard: bisherige Zahlungsart."},"payment_date":{"type":"string","format":"date","description":"Alias: `paymentDate`. Standard: bisheriges Datum."},"reference":{"type":"string","nullable":true},"notes":{"type":"string","nullable":true}}}}}},"responses":{"200":{"description":"200 { data: { …Zahlung, invoice_id/invoiceId, invoice_number/invoiceNumber, invoice_status/invoiceStatus, total_paid/totalPaid, amount_due/amountDue } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:write"]},"delete":{"operationId":"delete_invoices_id_payments_paymentId","tags":["invoices"],"summary":"Zahlung löschen","description":"**Scope:** `invoices:write`\n\n**Antwort:** 200 { data: { id, deleted: true, invoice_id/invoiceId, invoice_number/invoiceNumber, invoice_status/invoiceStatus, total_paid/totalPaid, amount_due/amountDue } }\n\n- War die Rechnung PAID und ist nach dem Löschen wieder offen, fällt sie auf SENT zurück und verliert paidAt. Auf stornierten Rechnungen bewusst erlaubt (Fehlbuchung entfernen).\n- Audit PAYMENT_DELETED_VIA_API. 404 Payment not found.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}},{"name":"paymentId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"200 { data: { id, deleted: true, invoice_id/invoiceId, invoice_number/invoiceNumber, invoice_status/invoiceStatus, total_paid/totalPaid, amount_due/amountDue } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:write"]}},"/api/v1/invoices/{id}/dunning":{"get":{"operationId":"get_invoices_id_dunning","tags":["invoices"],"summary":"Mahnstatus und Mahnhistorie lesen","description":"**Scope:** `invoices:read`\n\n**Antwort:** { data: { invoice_id, invoice_number, status, due_date, days_overdue, gross_total, customer, dunning: { is_paused, paused_at, paused_reason, current_level, next_level/nextLevel, max_level_reached, total_reminders, total_fees, total_interest, total_outstanding }, reminders: [...] } }\n\n- next_level/nextLevel/max_level_reached sind neu; next_level ist die Stufe, die action: \"send\" als Nächstes verschickt (Stufe 3 bleibt Stufe 3).","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"{ data: { invoice_id, invoice_number, status, due_date, days_overdue, gross_total, customer, dunning: { is_paused, paused_at, paused_reason, current_level, next_level/nextLevel, max_level_reached, total_reminders, total_fees, total_interest, total_outstanding }, reminders: [...] } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:read"]},"post":{"operationId":"post_invoices_id_dunning","tags":["invoices"],"summary":"Mahnwesen pausieren/fortsetzen oder eine Mahnstufe versenden","description":"**Scope:** `invoices:write`\n\n**Antwort:** pause/resume: 200 { data: { invoice_id, invoice_number, dunning_paused, dunning_paused_at, dunning_paused_reason } }. send: 201 { data: { id, invoice_id/invoiceId, level, sent_at/sentAt, sent_to/sentTo, fee, interest_rate/interestRate, interest_amount/interestAmount, total_amount/totalAmount, days_overdue/daysOverdue, remaining_amount/remainingAmount } }\n\n- pause/resume: setzt dunningPaused/-At/-Reason, Audit DUNNING_PAUSED_VIA_API / DUNNING_RESUMED_VIA_API.\n- send: verschickt die gebrandete Mahn-E-Mail (Stufe 1 Erinnerung, 2 = 1. Mahnung, 3 = letzte Mahnung), legt eine Reminder-Zeile an, setzt SENT → OVERDUE (Webhook invoice.overdue) und löst reminder.sent sowie ab Stufe 2 reminder.escalated aus. Audit REMINDER_SENT_VIA_API.\n- 400 ungültige action/Feldtypen; 400 (code invoice_status) wenn Rechnung nicht SENT/OVERDUE; 400 (code no_recipient) ohne Kundenmailadresse; 409 dunning_paused/max_level_reached/already_paid; 502 (code email_failed) bei fehlgeschlagenem Versand (keine Reminder-Zeile).","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"action":{"type":"string","enum":["pause","resume","send"],"description":"pause | resume | send."},"reason":{"type":"string","description":"Nur bei pause gespeichert."},"recipient_email":{"type":"string","description":"Nur bei send. Alias: `recipientEmail`. Standard: E-Mail des Kunden."},"custom_reminder_fee":{"type":"number","description":"Nur bei send, ≥ 0. Alias: `customReminderFee`. Standard: Company.reminderFee1/2/3 je Stufe."},"include_interest":{"type":"boolean","default":false,"description":"Nur bei send; Zinssatz aus Company.defaultInterestRate, einfache Verzinsung Restbetrag × Satz × Tage/365. Alias: `includeInterest`."}},"required":["action"]}}}},"responses":{"201":{"description":"pause/resume: 200 { data: { invoice_id, invoice_number, dunning_paused, dunning_paused_at, dunning_paused_reason } }. send: 201 { data: { id, invoice_id/invoiceId, level, sent_at/sentAt, sent_to/sentTo, fee, interest_rate/interestRate, interest_amount/interestAmount, total_amount/totalAmount, days_overdue/daysOverdue, remaining_amount/remainingAmount } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:write"]}},"/api/v1/invoices/{id}/correct":{"post":{"operationId":"post_invoices_id_correct","tags":["invoices"],"summary":"Berichtigung nach § 31 Abs. 5 UStDV anlegen (neue Version)","description":"**Scope:** `invoices:write`\n\n**Antwort:** 200 { data: { invoiceId, invoiceNumber, versionNumber, changeReason, lazyInitialVersion } }\n\n- Rechnungsnummer bleibt, neue DocumentVersion (CORRECTION) mit vollständigem Snapshot, Steuerlogik und Summen neu berechnet, PDF wird neu erzeugt und archiviert; bei Bestandsrechnungen ohne lockedAt wird v1 nachträglich erzeugt (lazyInitialVersion: true).\n- Audit DOCUMENT_CORRECTED, Webhook invoice.updated. 400 fehlender/zu kurzer change_reason; 400 (code not_locked) — dann PUT verwenden; 400 ungültiges Enum/Datum/Zahl oder leere lines; 404 Rechnung/Kunde unbekannt/fremd.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"change_reason":{"type":"string","description":"Mindestens MIN_CHANGE_REASON_LENGTH Zeichen. Alias: `changeReason`."},"customer_id":{"type":"string","description":"Kundenwechsel. Alias: `customerId`."},"issue_date":{"type":"string","format":"date"},"due_date":{"type":"string","format":"date"},"service_period_start":{"type":"string","format":"date"},"service_period_end":{"type":"string","format":"date"},"reverse_charge":{"type":"boolean"},"tax_exemption_reason":{"type":"string","enum":["NONE","SMALL_BUSINESS","REVERSE_CHARGE_EU","REVERSE_CHARGE_EU_NO_VAT","THIRD_COUNTRY","INTRA_COMMUNITY","EXPORT_DELIVERY","REVERSE_CHARGE_13B","INTERNAL_SUPPLY","OTHER_EXEMPTION"]},"tax_exemption_note":{"type":"string"},"tax_country":{"type":"string"},"delivery_country":{"type":"string"},"oss_country":{"type":"string"},"oss_applicable":{"type":"boolean"},"header_text":{"type":"string"},"notes":{"type":"string"},"footer":{"type":"string"},"order_number":{"type":"string"},"buyer_reference":{"type":"string"},"lines":{"type":"array","items":{"type":"object"},"description":"Ersetzt die komplette Positionsliste; je Position description (Pflicht), quantity (Default 1), unit_price (Default 0), tax_rate (Default 0), discount (absolut, Default 0), line_type (Default PRODUCT) Zulässige Werte: PRODUCT, SERVICE, DISCOUNT_PERCENT, DISCOUNT_ABSOLUTE, SHIPPING, ADVANCE_PAYMENT, CREDIT, TEXT, SUBTOTAL, PAGEBREAK."}},"required":["change_reason"]}}}},"responses":{"200":{"description":"200 { data: { invoiceId, invoiceNumber, versionNumber, changeReason, lazyInitialVersion } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:write"]}},"/api/v1/invoices/{id}/versions":{"get":{"operationId":"get_invoices_id_versions","tags":["invoices"],"summary":"Versionshistorie einer Rechnung lesen","description":"**Scope:** `invoices:read`\n\n**Antwort:** { data: { invoiceId, currentVersion, versions: [{ id, versionNumber, changeType, changeReason, hasPdf, createdAt, createdBy }] } }, absteigend nach versionNumber.\n\n- Gleiche Felder wie die Dashboard-Route /api/documents/invoice/{id}/versions. 404 Invoice not found.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"{ data: { invoiceId, currentVersion, versions: [{ id, versionNumber, changeType, changeReason, hasPdf, createdAt, createdBy }] } }, absteigend nach versionNumber.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:read"]}},"/api/v1/invoices/{id}/versions/{version}/pdf":{"get":{"operationId":"get_invoices_id_versions_version_pdf","tags":["invoices"],"summary":"Archiviertes PDF einer bestimmten Version laden","description":"**Scope:** `invoices:read`\n\n**Antwort:** 200 PDF-Bytes, Content-Disposition inline; filename=\"{Belegnummer}-v{n}.pdf\", Cache-Control private, max-age=3600, immutable.\n\n- Liefert ausschließlich das archivierte PDF, nie neu gerendert (GoBD). Audit DOCUMENT_VERSION_PDF_DOWNLOADED.\n- 400 Invalid version number; 404 Invoice/Version not found bzw. PDF für diese Version nicht verfügbar/nicht im Storage.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}},{"name":"version","in":"path","required":true,"schema":{"type":"string"}},{"name":"version","in":"query","required":true,"description":"Versionsnummer ≥ 1.","schema":{"type":"number"}}],"responses":{"200":{"description":"200 PDF-Bytes, Content-Disposition inline; filename=\"{Belegnummer}-v{n}.pdf\", Cache-Control private, max-age=3600, immutable.","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:read"]}},"/api/v1/invoices/{id}/facturx":{"get":{"operationId":"get_invoices_id_facturx","tags":["invoices"],"summary":"Factur-X/ZUGFeRD-XML herunterladen","description":"**Scope:** `invoices:read`\n\n**Antwort:** 200 XML, Content-Type application/xml; charset=utf-8, Content-Disposition attachment.\n\n- Factur-X/ZUGFeRD 2.3 (EN 16931, CII, Profil BASIC) — identisch zum Dashboard-Download. Keine Statusschranke: auch Entwürfe, Gutschriften (TypeCode 381) und Proformas (325) liefern XML.\n- Interne notes bleiben draußen, Steuerhinweis und Schlusstext sind enthalten. Audit EINVOICE_XML_DOWNLOADED_VIA_API. 404 Invoice not found.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"200 XML, Content-Type application/xml; charset=utf-8, Content-Disposition attachment.","content":{"application/xml":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:read"]}},"/api/v1/credit-notes":{"get":{"operationId":"get_credit_notes","tags":["credit-notes"],"summary":"Gutschriften auflisten","description":"**Scope:** `invoices:read`\n\n**Antwort:** { data: CreditNoteListItem[], pagination: { total, limit, offset, hasMore, has_more } }\n\n- payout_status filtert jetzt in der Datenbankabfrage statt nach der Seitenbildung — total/has_more passen zum Ergebnis.\n- Ungültiges status/payout_status → 400 mit allowedValues (vorher 500 aus Prisma). Sortierung issueDate absteigend.","parameters":[{"name":"limit","in":"query","required":false,"description":"Seitengröße.","schema":{"type":"number","default":50}},{"name":"offset","in":"query","required":false,"description":"Versatz.","schema":{"type":"number","default":0}},{"name":"recipient_id","in":"query","required":false,"description":"Filtert nach Empfänger.","schema":{"type":"string"}},{"name":"customer_id","in":"query","required":false,"description":"Filtert nach Empfänger.","schema":{"type":"string"}},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["DRAFT","FINAL","SENT","PAID","OVERDUE","CANCELLED"]}},{"name":"payout_status","in":"query","required":false,"description":"Filtert in der Abfrage (payout.is.status)","schema":{"type":"string","enum":["PENDING","APPROVED","EXECUTED","FAILED","CANCELLED"]}},{"name":"category_id","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"{ data: CreditNoteListItem[], pagination: { total, limit, offset, hasMore, has_more } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/CreditNoteListItem"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:read"]},"post":{"operationId":"post_credit_notes","tags":["credit-notes"],"summary":"Gutschrift anlegen (frei oder verknüpft mit einer Rechnung)","description":"**Scope:** `invoices:write`\n\n**Antwort:** 201 { data: { id, invoice_number/invoiceNumber, numberIssued, credit_note_reason/creditNoteReason, status, issue_date, recipient, currency, net_total, tax_total, gross_total, lines[], notes, footer, metadata, parent_invoice_id, payout, created_at } }\n\n- Neue Gutschrift ist immer ein Entwurf mit Platzhalternummer ENTWURF-…; die echte GS-Nummer vergibt erst das Ausstellen (PUT-Statuswechsel, POST …/send oder POST /credit-notes/{id}/execute).\n- Verknüpfter Modus (parent_invoice_id ohne lines) spiegelt createCreditNote: nur für Rechnungen im Status SENT/PAID/OVERDUE (sonst 409); Steuerangaben und Belegkategorie kommen von der Ursprungsrechnung.\n- Beträge und Mengen sind negativ gespeichert (wie im Dashboard); payout.amount ist positiv.\n- 404 fremde/fehlende Ursprungsrechnung oder Empfänger. Audit CREDIT_NOTE_CREATED_VIA_API, Webhook credit_note.created.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"recipient_id":{"type":"string","description":"Pflicht außer parent_invoice_id ist gesetzt. Alias: `recipientId`."},"reason":{"type":"string","enum":["PAYOUT","COMMISSION","GOODWILL","RETURN","PRICE_CORRECTION","OTHER"],"default":"PAYOUT","description":"CANCELLATION ist nicht erlaubt (→ 400)"},"lines":{"type":"array","items":{"type":"object"},"description":"Pflicht außer im verknüpften Modus (parent_invoice_id ohne lines)"},"parent_invoice_id":{"type":"string","description":"Verknüpfter Modus. Alias: `parentInvoiceId`."},"partial_lines":{"type":"array","items":{"type":"object","properties":{"line_id":{},"quantity":{}}},"description":"Nur im verknüpften Modus ohne lines; sonst werden alle Positionen gutgeschrieben. Alias: `partialLines`."},"issue_date":{"type":"string","format":"date","description":"Alias: `issueDate`. Standard: heute."},"currency":{"type":"string","description":"Standard: Währung der Ursprungsrechnung bzw. EUR."},"reverse_charge":{"type":"boolean","description":"Alias: `reverseCharge`."},"tax_exemption_reason":{"type":"string","enum":["NONE","SMALL_BUSINESS","REVERSE_CHARGE_EU","REVERSE_CHARGE_EU_NO_VAT","THIRD_COUNTRY","INTRA_COMMUNITY","EXPORT_DELIVERY","REVERSE_CHARGE_13B","INTERNAL_SUPPLY","OTHER_EXEMPTION"],"description":"Alias: `taxExemptionReason`."},"notes":{"type":"string","description":"Wird unverändert gespeichert; erzeugt keinen Text aus dem Grund."},"footer":{"type":"string","nullable":true,"description":"Fehlt/null → notes wird zusätzlich als Schlusstext verwendet; leerer String setzt bewusst keinen."},"metadata":{"type":"object"},"create_payout":{"type":"boolean","default":false,"description":"Alias: `createPayout`."},"payout_iban":{"type":"string","description":"Ohne IBAN (weder Body noch Kunde) → 400."},"payout_bic":{"type":"string","description":"Ohne IBAN (weder Body noch Kunde) → 400."},"payout_account_holder":{"type":"string","description":"Ohne IBAN (weder Body noch Kunde) → 400."},"payout_bank_name":{"type":"string","description":"Ohne IBAN (weder Body noch Kunde) → 400."},"payout_reference":{"type":"string","description":"Ohne IBAN (weder Body noch Kunde) → 400."}}}}}},"responses":{"201":{"description":"201 { data: { id, invoice_number/invoiceNumber, numberIssued, credit_note_reason/creditNoteReason, status, issue_date, recipient, currency, net_total, tax_total, gross_total, lines[], notes, footer, metadata, parent_invoice_id, payout, created_at } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:write"]}},"/api/v1/credit-notes/{id}":{"get":{"operationId":"get_credit_notes_id","tags":["credit-notes"],"summary":"Gutschriftsdetails lesen","description":"**Scope:** `invoices:read`\n\n**Antwort:** { data: { id, invoice_number/invoiceNumber, numberIssued, credit_note_reason/creditNoteReason, status, issue_date, due_date, recipient (mit Bankdaten), currency, net_total, tax_total, gross_total, reverse_charge, tax_exemption_reason, lines[], notes, footer, metadata, parent_invoice, payout, created_at, updated_at } }\n\n- 404 bei fremder/fehlender Gutschrift.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"{ data: { id, invoice_number/invoiceNumber, numberIssued, credit_note_reason/creditNoteReason, status, issue_date, due_date, recipient (mit Bankdaten), currency, net_total, tax_total, gross_total, reverse_charge, tax_exemption_reason, lines[], notes, footer, metadata, parent_invoice, payout, created_at, updated_at } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:read"]},"put":{"operationId":"put_credit_notes_id","tags":["credit-notes"],"summary":"Gutschrift im Entwurf ändern","description":"**Scope:** `invoices:write`\n\n**Antwort:** { data: { id, invoice_number/invoiceNumber, numberIssued, credit_note_reason/creditNoteReason, status, currency, issue_date, net_total, tax_total, gross_total, lines[], notes, footer, metadata, updated_at } }\n\n- Nur im Status DRAFT (sonst 400). Positionsaustausch und Summen laufen in einer Transaktion.\n- Audit CREDIT_NOTE_UPDATED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"notes":{"type":"string"},"footer":{"type":"string","nullable":true},"metadata":{"type":"object"},"reason":{"type":"string","enum":["PAYOUT","COMMISSION","GOODWILL","RETURN","PRICE_CORRECTION","OTHER"],"description":"Ohne CANCELLATION."},"issue_date":{"type":"string","format":"date","description":"Alias: `issueDate`."},"currency":{"type":"string"},"reverse_charge":{"type":"boolean","description":"Alias: `reverseCharge`."},"tax_exemption_reason":{"type":"string","enum":["NONE","SMALL_BUSINESS","REVERSE_CHARGE_EU","REVERSE_CHARGE_EU_NO_VAT","THIRD_COUNTRY","INTRA_COMMUNITY","EXPORT_DELIVERY","REVERSE_CHARGE_13B","INTERNAL_SUPPLY","OTHER_EXEMPTION"],"description":"Alias: `taxExemptionReason`."},"lines":{"type":"array","items":{"type":"object"},"description":"Ersetzt die Positionen; Summen über calculateDocumentTotals neu berechnet (negativ)"}}}}}},"responses":{"200":{"description":"{ data: { id, invoice_number/invoiceNumber, numberIssued, credit_note_reason/creditNoteReason, status, currency, issue_date, net_total, tax_total, gross_total, lines[], notes, footer, metadata, updated_at } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:write"]}},"/api/v1/credit-notes/{id}/execute":{"post":{"operationId":"post_credit_notes_id_execute","tags":["credit-notes"],"summary":"Auszahlung ausführen (Gutschrift → PAID)","description":"**Scope:** `invoices:write`\n\n**Antwort:** 200 { data: { id, credit_note_id, credit_note_number, status, amount, currency, reference, iban, bic, transaction_id, executed_at, credit_note_status } }\n\n- Setzt die Auszahlung auf EXECUTED und die Gutschrift auf PAID. Trägt die Gutschrift noch die Platzhalternummer, wird dabei die echte GS-Nummer vergeben (Transaktion); eine Platzhalter-payout.reference wird auf die GS-Nummer gesetzt.\n- 400 ohne Auszahlung/bereits ausgeführt/abgebrochen; 409 parallel ausgestellt. Audit PAYOUT_EXECUTED_VIA_API, Webhooks payout.executed + credit_note.paid.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"transaction_id":{"type":"string","description":"Alias: `transactionId`."},"executed_at":{"type":"string","format":"date","description":"Alias: `executedAt`. Standard: jetzt."},"notes":{"type":"string","description":"Wird an payout.description angehängt."}}}}}},"responses":{"200":{"description":"200 { data: { id, credit_note_id, credit_note_number, status, amount, currency, reference, iban, bic, transaction_id, executed_at, credit_note_status } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:write"]},"put":{"operationId":"put_credit_notes_id_execute","tags":["credit-notes"],"summary":"Auszahlung freigeben/abbrechen/als fehlgeschlagen markieren","description":"**Scope:** `invoices:write`\n\n**Antwort:** { data: { id, credit_note_id, status, amount, approved_at, approved_by, failure_reason } }\n\n- cancel setzt die Gutschrift auf CANCELLED — ein Entwurf bekommt dabei bewusst keine Belegnummer.\n- Audit PAYOUT_APPROVE|CANCEL|FAIL_VIA_API, Webhooks payout.approved / payout.cancelled / payout.failed.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"action":{"type":"string","enum":["approve","cancel","fail"],"description":"approve nur aus PENDING, fail nur aus APPROVED, cancel nicht aus EXECUTED."},"failure_reason":{"type":"string","description":"Alias: `failureReason`."},"approved_by":{"type":"string","description":"Alias: `approvedBy`. Standard: \"API\"."}},"required":["action"]}}}},"responses":{"200":{"description":"{ data: { id, credit_note_id, status, amount, approved_at, approved_by, failure_reason } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:write"]}},"/api/v1/customers":{"get":{"operationId":"get_customers","tags":["customers"],"summary":"Kunden auflisten","description":"**Scope:** `customers:read`\n\n**Antwort:** { data: Customer[], pagination }\n\n- Archivierte Kunden fehlen ohne include_archived/archived jetzt in der Liste (wie in der Dashboard-Kundenliste). Sortierung name aufsteigend.","parameters":[{"name":"limit","in":"query","required":false,"description":"1–100.","schema":{"type":"number","default":50}},{"name":"offset","in":"query","required":false,"schema":{"type":"number","default":0}},{"name":"search","in":"query","required":false,"description":"Case-insensitiver Treffer in name, email, customerNumber.","schema":{"type":"string"}},{"name":"include_archived","in":"query","required":false,"description":"Archivierte Kunden zusätzlich anzeigen. Alias: `includeArchived`.","schema":{"type":"boolean"}},{"name":"archived","in":"query","required":false,"description":"true = nur archivierte, false = nur aktive.","schema":{"type":"boolean"}}],"responses":{"200":{"description":"{ data: Customer[], pagination }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Customer"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["customers:read"]},"post":{"operationId":"post_customers","tags":["customers"],"summary":"Kunde anlegen","description":"**Scope:** `customers:write`\n\n**Antwort:** 201 { data: Customer }\n\n- Plan-Gate checkCustomerLimit → 403 { code: \"plan_limit_reached\", limit, current }.\n- Doppelte E-Mail → 409. Vergebene customerNumber → 409 { code: \"CUSTOMER_NUMBER_TAKEN\" }, erschöpfter Nummernraum → 409 { code: \"CUSTOMER_NUMBER_UNAVAILABLE\" }.\n- taxIdValidated kommt nie aus der Eingabe: nur gesetzt, wenn dieselbe Firma dieselbe UID innerhalb der letzten Stunde erfolgreich über VIES geprüft hat.\n- Audit CUSTOMER_CREATED_VIA_API, Webhook customer.created.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Nicht leer."},"customerNumber":{"type":"string","description":"Leer/fehlend = automatische Vergabe (lib/customer-number.ts)"},"salutation":{"type":"string"},"title":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"department":{"type":"string"},"email":{"type":"string"},"phone":{"type":"string"},"mobile":{"type":"string"},"website":{"type":"string"},"taxId":{"type":"string"},"taxNumber":{"type":"string"},"registryCourt":{"type":"string"},"registryNumber":{"type":"string"},"bmdAccountNumber":{"type":"string","description":"Nur Ziffern, max. 9 Stellen."},"bmdRevenueAccount":{"type":"string","description":"Nur Ziffern, max. 9 Stellen."},"datevId":{"type":"string"},"defaultTaxExemptionReason":{"type":"string","nullable":true,"enum":["NONE","SMALL_BUSINESS","REVERSE_CHARGE_EU","REVERSE_CHARGE_EU_NO_VAT","THIRD_COUNTRY","INTRA_COMMUNITY","EXPORT_DELIVERY","REVERSE_CHARGE_13B","INTERNAL_SUPPLY","OTHER_EXEMPTION"]},"defaultTaxExemptionNote":{"type":"string"},"street":{"type":"string","description":"Rechnungsadresse."},"zipCode":{"type":"string","description":"Rechnungsadresse."},"city":{"type":"string","description":"Rechnungsadresse."},"state":{"type":"string","description":"Rechnungsadresse."},"countryCode":{"type":"string","description":"Rechnungsadresse."},"country":{"type":"string","description":"Leerer Wert ändert nichts. Standard: Österreich."},"shippingName":{"type":"string","description":"Lieferadresse."},"shippingCompany":{"type":"string","description":"Lieferadresse."},"shippingStreet":{"type":"string","description":"Lieferadresse."},"shippingZipCode":{"type":"string","description":"Lieferadresse."},"shippingCity":{"type":"string","description":"Lieferadresse."},"shippingState":{"type":"string","description":"Lieferadresse."},"shippingCountry":{"type":"string","description":"Lieferadresse."},"shippingCountryCode":{"type":"string","description":"Lieferadresse."},"bankAccountHolder":{"type":"string"},"bankName":{"type":"string"},"iban":{"type":"string"},"bic":{"type":"string"},"sepaMandateReference":{"type":"string"},"sepaMandateDate":{"type":"string","format":"date"},"sepaMandateType":{"type":"string","enum":["CORE","B2B"]},"sepaMandateStatus":{"type":"string","enum":["ACTIVE","REVOKED","EXPIRED"]},"sepaSequenceType":{"type":"string","enum":["FRST","RCUR","OOFF","FNAL"]},"paymentTermDays":{"type":"number","description":"≥ 0."},"discountPercent":{"type":"number","description":"0–100."},"discountDays":{"type":"number","description":"≥ 0."},"notes":{"type":"string"},"category":{"type":"string"},"contacts":{"description":"null leert das Feld."},"billingPrefs":{"description":"null leert das Feld."},"metadata":{"description":"null leert das Feld."},"address":{"type":"string","description":"Freitext; wird aus den strukturierten Feldern abgeleitet, wenn welche übergeben wurden."},"shippingAddress":{"type":"string","description":"Freitext; wird aus den strukturierten Feldern abgeleitet, wenn welche übergeben wurden."}},"required":["name"]}}}},"responses":{"201":{"description":"201 { data: Customer }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Customer"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["customers:write"]}},"/api/v1/customers/{id}":{"get":{"operationId":"get_customers_id","tags":["customers"],"summary":"Kundendetails lesen","description":"**Scope:** `customers:read`\n\n**Antwort:** { data: Customer & { invoiceCount, recentInvoices: [...] } } (letzte 10 Belege)\n\n- 404 bei fremdem/fehlendem Kunden.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"{ data: Customer & { invoiceCount, recentInvoices: [...] } } (letzte 10 Belege)","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Customer"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["customers:read"]},"put":{"operationId":"put_customers_id","tags":["customers"],"summary":"Kunde ändern","description":"**Scope:** `customers:write`\n\n**Antwort:** { data: Customer }\n\n- Verhaltensänderung: die Route schrieb bisher nur 6 Felder (name, email, taxId, address, shippingAddress, contacts) und verwarf den Rest — jetzt volle Formularparität.\n- Doppelte E-Mail → 409, vergebene Kundennummer → 409 { code: \"CUSTOMER_NUMBER_TAKEN\" }. Geänderte taxId setzt taxIdValidated zurück (bzw. übernimmt eine ≤1h alte VIES-Prüfung).\n- Audit CUSTOMER_UPDATED_VIA_API, Webhook customer.updated.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Nicht leer."},"customerNumber":{"type":"string","description":"Leer/fehlend = automatische Vergabe (lib/customer-number.ts)"},"salutation":{"type":"string"},"title":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"department":{"type":"string"},"email":{"type":"string"},"phone":{"type":"string"},"mobile":{"type":"string"},"website":{"type":"string"},"taxId":{"type":"string"},"taxNumber":{"type":"string"},"registryCourt":{"type":"string"},"registryNumber":{"type":"string"},"bmdAccountNumber":{"type":"string","description":"Nur Ziffern, max. 9 Stellen."},"bmdRevenueAccount":{"type":"string","description":"Nur Ziffern, max. 9 Stellen."},"datevId":{"type":"string"},"defaultTaxExemptionReason":{"type":"string","nullable":true,"enum":["NONE","SMALL_BUSINESS","REVERSE_CHARGE_EU","REVERSE_CHARGE_EU_NO_VAT","THIRD_COUNTRY","INTRA_COMMUNITY","EXPORT_DELIVERY","REVERSE_CHARGE_13B","INTERNAL_SUPPLY","OTHER_EXEMPTION"]},"defaultTaxExemptionNote":{"type":"string"},"street":{"type":"string","description":"Rechnungsadresse."},"zipCode":{"type":"string","description":"Rechnungsadresse."},"city":{"type":"string","description":"Rechnungsadresse."},"state":{"type":"string","description":"Rechnungsadresse."},"countryCode":{"type":"string","description":"Rechnungsadresse."},"country":{"type":"string","description":"Leerer Wert ändert nichts. Standard: Österreich."},"shippingName":{"type":"string","description":"Lieferadresse."},"shippingCompany":{"type":"string","description":"Lieferadresse."},"shippingStreet":{"type":"string","description":"Lieferadresse."},"shippingZipCode":{"type":"string","description":"Lieferadresse."},"shippingCity":{"type":"string","description":"Lieferadresse."},"shippingState":{"type":"string","description":"Lieferadresse."},"shippingCountry":{"type":"string","description":"Lieferadresse."},"shippingCountryCode":{"type":"string","description":"Lieferadresse."},"bankAccountHolder":{"type":"string"},"bankName":{"type":"string"},"iban":{"type":"string"},"bic":{"type":"string"},"sepaMandateReference":{"type":"string"},"sepaMandateDate":{"type":"string","format":"date"},"sepaMandateType":{"type":"string","enum":["CORE","B2B"]},"sepaMandateStatus":{"type":"string","enum":["ACTIVE","REVOKED","EXPIRED"]},"sepaSequenceType":{"type":"string","enum":["FRST","RCUR","OOFF","FNAL"]},"paymentTermDays":{"type":"number","description":"≥ 0."},"discountPercent":{"type":"number","description":"0–100."},"discountDays":{"type":"number","description":"≥ 0."},"notes":{"type":"string"},"category":{"type":"string"},"contacts":{"description":"null leert das Feld."},"billingPrefs":{"description":"null leert das Feld."},"metadata":{"description":"null leert das Feld."},"address":{"type":"string","description":"Freitext; wird aus den strukturierten Feldern abgeleitet, wenn welche übergeben wurden."},"shippingAddress":{"type":"string","description":"Freitext; wird aus den strukturierten Feldern abgeleitet, wenn welche übergeben wurden."}},"description":"(alle Felder von POST): Alle optional, PATCH-Semantik: nur mitgeschickte Felder werden geschrieben. customerNumber: \"\"/null entfernt die Nummer (keine automatische Neuvergabe)."}}}},"responses":{"200":{"description":"{ data: Customer }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Customer"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["customers:write"]},"delete":{"operationId":"delete_customers_id","tags":["customers"],"summary":"Kunde löschen","description":"**Scope:** `customers:write`\n\n**Antwort:** { success: true }\n\n- Nur ungenutzte Kunden (lib/customer-lifecycle.ts). Jede Verknüpfung → 409 { code: \"CUSTOMER_IN_USE\" }, Empfehlung: archivieren.\n- Webhook customer.deleted.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"{ success: true }","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["customers:write"]}},"/api/v1/customers/{id}/archive":{"post":{"operationId":"post_customers_id_archive","tags":["customers"],"summary":"Kunde archivieren","description":"**Scope:** `customers:write`\n\n**Antwort:** 200 { data: Customer }\n\n- Body wird ignoriert. Setzt isArchived=true + archivedAt=now. Audit CUSTOMER_ARCHIVED_VIA_API, Webhook customer.updated. 404 bei fremdem Kunden.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"200 { data: Customer }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Customer"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["customers:write"]}},"/api/v1/customers/{id}/unarchive":{"post":{"operationId":"post_customers_id_unarchive","tags":["customers"],"summary":"Kunde aus dem Archiv zurückholen","description":"**Scope:** `customers:write`\n\n**Antwort:** 200 { data: Customer }\n\n- Body wird ignoriert. Setzt isArchived=false + archivedAt=null. Audit CUSTOMER_UNARCHIVED_VIA_API, Webhook customer.updated.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"200 { data: Customer }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Customer"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["customers:write"]}},"/api/v1/customers/{id}/validate-vat":{"post":{"operationId":"post_customers_id_validate_vat","tags":["customers"],"summary":"UID über VIES prüfen und am Kunden speichern","description":"**Scope:** `customers:write`\n\n**Antwort:** 200 { data: { valid, unavailable, countryCode, vatNumber, requestDate, name, address, errorMessage, customer: Customer } }\n\n- Ruft VIES (lib/vies.ts), schreibt eine VatValidation-Zeile und speichert taxId (normalisiert), taxIdValidated, taxIdValidatedAt am Kunden.\n- Ohne UID → 400. Audit CUSTOMER_VAT_VALIDATED_VIA_API, Webhook customer.updated. Ergebnis gilt eine Stunde als Grundlage für ein nachfolgendes POST/PUT mit derselben UID.\n- unavailable=true bedeutet, dass VIES nicht erreichbar war (Zeitüberschreitung, Wartung, Netzfehler). valid=false sagt dann nichts über die UID aus; der gespeicherte Stand des Kunden bleibt unverändert, sofern er zur geprüften UID gehört.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"vatNumber":{"type":"string","description":"Alias: `vat_number`."},"taxId":{"type":"string","description":"Fehlen beide, wird die gespeicherte UID geprüft. Alias: `tax_id`."}}}}}},"responses":{"200":{"description":"200 { data: { valid, unavailable, countryCode, vatNumber, requestDate, name, address, errorMessage, customer: Customer } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["customers:write"]}},"/api/v1/customers/duplicates":{"get":{"operationId":"get_customers_duplicates","tags":["customers"],"summary":"Ähnliche Kunden anhand des Namens finden","description":"**Scope:** `customers:read`\n\n**Antwort:** { data: [{ id, name, customerNumber, city, isArchived, similarity }] } (max. 5, absteigend nach similarity)\n\n- Unscharfer Vergleich ≥ 0,65 (lib/customer-duplicates.ts), archivierte Kunden zählen mit. Reine Leseabfrage.","parameters":[{"name":"name","in":"query","required":true,"description":"Max. 200 Zeichen; unter 3 Zeichen liefert {data:[]}.","schema":{"type":"string"}},{"name":"exclude_id","in":"query","required":false,"description":"Alias: `excludeId`.","schema":{"type":"string"}}],"responses":{"200":{"description":"{ data: [{ id, name, customerNumber, city, isArchived, similarity }] } (max. 5, absteigend nach similarity)","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"type":"object"}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["customers:read"]}},"/api/v1/customers/by-email/{email}":{"get":{"operationId":"get_customers_by_email_email","tags":["customers"],"summary":"Kunde per E-Mail-Adresse finden","description":"**Scope:** `customers:read`\n\n**Antwort:** { data: Customer & { invoiceCount } }\n\n- Vergleich ohne Groß-/Kleinschreibung, 404 wenn kein Treffer. Archivierte Kunden werden weiterhin gefunden.","parameters":[{"name":"email","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"{ data: Customer & { invoiceCount } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Customer"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["customers:read"]}},"/api/v1/products":{"get":{"operationId":"get_products","tags":["products"],"summary":"Produkte auflisten","description":"**Scope:** `products:read`\n\n**Antwort:** { data: Product[], pagination }\n\n- Kein Plan-Gate.\n- include_inactive=false ist der neue Opt-in für nur aktive Produkte.","parameters":[{"name":"search","in":"query","required":false,"description":"Trifft name/sku, case-insensitiv.","schema":{"type":"string"}},{"name":"isService","in":"query","required":false,"description":"Alias: `is_service`.","schema":{"type":"boolean"}},{"name":"include_inactive","in":"query","required":false,"description":"Bewusste Abweichung: Default true, damit bestehende Integrationen weiter alle Produkte sehen (die Route filterte vorher nie nach isActive)","schema":{"type":"boolean","default":true}},{"name":"limit","in":"query","required":false,"schema":{"type":"number","default":50}},{"name":"offset","in":"query","required":false,"schema":{"type":"number","default":0}}],"responses":{"200":{"description":"{ data: Product[], pagination }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Product"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["products:read"]},"post":{"operationId":"post_products","tags":["products"],"summary":"Produkt anlegen","description":"**Scope:** `products:write`\n\n**Antwort:** 201 { data: { id, name, description, sku, isService, price, taxRate, unit, isActive, createdAt, updatedAt } }\n\n- Audit PRODUCT_CREATED_VIA_API. Antwort enthält seit diesem Umbau zusätzlich updatedAt.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"sku":{"type":"string","description":"Eindeutig je Firma → 409 wenn vergeben."},"isService":{"type":"boolean","default":false,"description":"Alias: `is_service`."},"price":{"type":"number","description":"Muss ≥ 0 sein."},"taxRate":{"type":"number","default":20,"description":"Muss 0–100 sein. Alias: `tax_rate`."},"unit":{"type":"string","description":"Standard: \"Stück\"."},"isActive":{"type":"boolean","default":true,"description":"Alias: `is_active`."}},"required":["name","price"]}}}},"responses":{"201":{"description":"201 { data: { id, name, description, sku, isService, price, taxRate, unit, isActive, createdAt, updatedAt } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["products:write"]}},"/api/v1/products/{id}":{"get":{"operationId":"get_products_id","tags":["products"],"summary":"Produktdetails lesen","description":"**Scope:** `products:read`\n\n**Antwort:** { data: { id, name, description, sku, isService, price, taxRate, unit, isActive, createdAt, updatedAt } }\n\n- 404 wenn nicht gefunden. Deaktivierte Produkte bleiben per ID lesbar.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"{ data: { id, name, description, sku, isService, price, taxRate, unit, isActive, createdAt, updatedAt } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["products:read"]},"put":{"operationId":"put_products_id","tags":["products"],"summary":"Produkt ändern","description":"**Scope:** `products:write`\n\n**Antwort:** 200 { data: { id, name, description, sku, isService, price, taxRate, unit, isActive, createdAt, updatedAt } }\n\n- 404 wenn nicht gefunden, 409 bei SKU-Kollision mit einem anderen Produkt. Audit PRODUCT_UPDATED_VIA_API. Antwort enthält seit diesem Umbau zusätzlich createdAt.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"sku":{"type":"string","description":"Eindeutig je Firma → 409 wenn vergeben."},"isService":{"type":"boolean","default":false,"description":"Alias: `is_service`."},"price":{"type":"number","description":"Muss ≥ 0 sein."},"taxRate":{"type":"number","default":20,"description":"Muss 0–100 sein. Alias: `tax_rate`."},"unit":{"type":"string","description":"Standard: \"Stück\"."},"isActive":{"type":"boolean","default":true,"description":"Alias: `is_active`."}},"description":"(alle Felder von POST): Alle optional/partiell, snake_case-Aliase via pickField. price/taxRate werden wie bei POST validiert."}}}},"responses":{"200":{"description":"200 { data: { id, name, description, sku, isService, price, taxRate, unit, isActive, createdAt, updatedAt } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["products:write"]},"delete":{"operationId":"delete_products_id","tags":["products"],"summary":"Produkt deaktivieren (Soft-Delete)","description":"**Scope:** `products:write`\n\n**Antwort:** 200 { success: true }\n\n- Setzt isActive: false, die Zeile bleibt erhalten. Audit PRODUCT_DEACTIVATED_VIA_API. 404 wenn nicht gefunden.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"200 { success: true }","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["products:write"]}},"/api/v1/quotes":{"get":{"operationId":"get_quotes","tags":["quotes"],"summary":"Angebote auflisten","description":"**Scope:** `quotes:read`\n\n**Antwort:** { data: Quote[], pagination }\n\n- Sortierung issueDate desc. EXPIRED entsteht nie in der Datenbank, nur in displayStatus.","parameters":[{"name":"status","in":"query","required":false,"description":"Filtert den gespeicherten Status.","schema":{"type":"string","enum":["DRAFT","SENT","ACCEPTED","REJECTED","EXPIRED","CONVERTED"]}},{"name":"customer_id","in":"query","required":false,"description":"Alias: `customerId`.","schema":{"type":"string"}},{"name":"search","in":"query","required":false,"description":"Case-insensitiv in Angebotsnummer und Kundenname.","schema":{"type":"string"}},{"name":"from","in":"query","required":false,"description":"Bereich über issueDate.","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","required":false,"description":"Bereich über issueDate.","schema":{"type":"string","format":"date"}},{"name":"category_id","in":"query","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"number","default":50}},{"name":"offset","in":"query","required":false,"schema":{"type":"number","default":0}}],"responses":{"200":{"description":"{ data: Quote[], pagination }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Quote"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["quotes:read"]},"post":{"operationId":"post_quotes","tags":["quotes"],"summary":"Angebot anlegen","description":"**Scope:** `quotes:write`\n\n**Antwort:** 201 { data: Quote }\n\n- Dokumentkategorie-Prüfung (403/400), Plan-Limit checkDocumentLimit (403), Kunde muss zur Firma gehören (404).\n- Angebotsnummer atomar aus dem Nummernkreis AN, Summen über calculateDocumentTotals (Prozent-Rabatte). Audit QUOTE_CREATED_VIA_API. Status immer DRAFT.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"customerId":{"type":"string"},"validUntil":{"type":"string","format":"date"},"issueDate":{"type":"string","format":"date","description":"Standard: jetzt."},"servicePeriodStart":{"type":"string","format":"date","nullable":true},"servicePeriodEnd":{"type":"string","format":"date","nullable":true},"documentCategoryId":{"type":"string","nullable":true},"reverseCharge":{"type":"boolean","default":false},"headerText":{"type":"string","description":"Wird gedruckt."},"notes":{"type":"string","description":"Intern, nicht auf dem PDF."},"footer":{"type":"string","description":"Wird gedruckt."},"lines":{"type":"array","items":{"type":"object"},"description":"Mindestens 1 Position; discount ist ein Prozentwert (0–100)"}},"required":["customerId","validUntil","lines"]}}}},"responses":{"201":{"description":"201 { data: Quote }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Quote"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["quotes:write"]}},"/api/v1/quotes/{id}":{"get":{"operationId":"get_quotes_id","tags":["quotes"],"summary":"Angebotsdetails lesen","description":"**Scope:** `quotes:read`\n\n**Antwort:** { data: Quote }\n\n- 404 \"Quote not found\" für unbekannte/fremde IDs.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"{ data: Quote }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Quote"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["quotes:read"]},"put":{"operationId":"put_quotes_id","tags":["quotes"],"summary":"Angebotsentwurf ändern","description":"**Scope:** `quotes:write`\n\n**Antwort:** { data: Quote }\n\n- Nur Entwürfe: DRAFT, lockedAt = null, convertedToInvoiceId = null — sonst 409. Das Dashboard hat keinen eigenen Bearbeiten-Pfad für Angebote, diese Route füllt die Lücke unter denselben Zustandsregeln wie das Löschen.\n- Kopf und Positionen werden bedingt in einer Transaktion geschrieben; eine zwischenzeitliche Änderung ergibt 409. Audit QUOTE_UPDATED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"customerId":{"description":"Alle optional; null bei documentCategoryId/servicePeriod* löscht das Feld."},"documentCategoryId":{"description":"Alle optional; null bei documentCategoryId/servicePeriod* löscht das Feld."},"issueDate":{"description":"Alle optional; null bei documentCategoryId/servicePeriod* löscht das Feld."},"validUntil":{"description":"Alle optional; null bei documentCategoryId/servicePeriod* löscht das Feld."},"servicePeriodStart":{"description":"Alle optional; null bei documentCategoryId/servicePeriod* löscht das Feld."},"servicePeriodEnd":{"description":"Alle optional; null bei documentCategoryId/servicePeriod* löscht das Feld."},"reverseCharge":{"description":"Alle optional; null bei documentCategoryId/servicePeriod* löscht das Feld."},"headerText":{"description":"Alle optional; null bei documentCategoryId/servicePeriod* löscht das Feld."},"notes":{"description":"Alle optional; null bei documentCategoryId/servicePeriod* löscht das Feld."},"footer":{"description":"Alle optional; null bei documentCategoryId/servicePeriod* löscht das Feld."},"lines":{"type":"array","items":{"type":"object"},"description":"Ersetzt alle Positionen."}}}}}},"responses":{"200":{"description":"{ data: Quote }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Quote"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["quotes:write"]},"delete":{"operationId":"delete_quotes_id","tags":["quotes"],"summary":"Angebotsentwurf löschen","description":"**Scope:** `quotes:write`\n\n**Antwort:** { data: { id, deleted: true } }\n\n- Nur DRAFT, nicht festgeschrieben, nicht umgewandelt (bedingtes deleteMany); sonst 409. Audit QUOTE_DELETED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"{ data: { id, deleted: true } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["quotes:write"]}},"/api/v1/quotes/{id}/status":{"post":{"operationId":"post_quotes_id_status","tags":["quotes"],"summary":"Angebot annehmen oder ablehnen","description":"**Scope:** `quotes:write`\n\n**Antwort:** 200 { data: Quote }\n\n- Verlässt der Beleg dabei den Entwurf, zählt er aufs Dokumentenkontingent des Plans: bei erschöpftem Kontingent 403.\n- CONVERTED ist nicht setzbar (400, „In Rechnung entsteht nur durch Umwandeln“); EXPIRED existiert nur als Anzeige.\n- Bereits umgewandeltes Angebot → 409; unzulässiger Wechsel (z. B. ACCEPTED → ACCEPTED) → 409; unbekannter Wert → 400 mit allowedValues. Audit QUOTE_STATUS_CHANGED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["ACCEPTED","REJECTED"],"description":"Nur ACCEPTED und REJECTED erlaubt."}},"required":["status"]}}}},"responses":{"200":{"description":"200 { data: Quote }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Quote"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["quotes:write"]}},"/api/v1/quotes/{id}/send":{"post":{"operationId":"post_quotes_id_send","tags":["quotes"],"summary":"Angebot per E-Mail versenden","description":"**Scope:** `quotes:write`\n\n**Antwort:** 200 { data: { quoteId, quoteNumber, status, sentTo, cc, bcc } }\n\n- Verlässt der Beleg dabei den Entwurf, zählt er aufs Dokumentenkontingent des Plans: bei erschöpftem Kontingent 403.\n- PDF über lib/quote-pdf.ts, E-Mail-Typ QUOTE mit Anhang; erst nach erfolgreichem Versand DRAFT → SENT (bedingt).\n- lockDocument(\"QUOTE\") + archiveDistributedPdfAsInitialVersion legen den versendeten Anhang als Version 1 ab. Kein Empfänger → 400; Versandfehler → 500. Audit QUOTE_SENT_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"recipientEmail":{"type":"string","description":"Alias: `recipient_email`. Standard: E-Mail des Kunden."},"cc":{"type":"string"},"bcc":{"type":"string"}}}}}},"responses":{"200":{"description":"200 { data: { quoteId, quoteNumber, status, sentTo, cc, bcc } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["quotes:write"]}},"/api/v1/quotes/{id}/pdf":{"get":{"operationId":"get_quotes_id_pdf","tags":["quotes"],"summary":"Angebots-PDF laden","description":"**Scope:** `quotes:read`\n\n**Antwort:** application/pdf (Binär), Content-Disposition inline, X-PDF-Source: s3-cache | generated.\n\n- Derselbe Renderer wie Dashboard und E-Mail (loadOrGenerateQuotePdf). Fingerprint-Cache aktiv, Entwürfe werden nie gecacht. Audit PDF_DOWNLOADED_VIA_API. 404 \"Quote not found\".","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"application/pdf (Binär), Content-Disposition inline, X-PDF-Source: s3-cache | generated.","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["quotes:read"]}},"/api/v1/quotes/{id}/convert":{"post":{"operationId":"post_quotes_id_convert","tags":["quotes"],"summary":"Angebot in Rechnung oder Auftragsbestätigung umwandeln","description":"**Scope:** `quotes:write`, `invoices:write`\n\n**Antwort:** 201 { data: { target, quoteId, invoice: {…} } } bzw. { target, quoteId, orderConfirmation } }\n\n- Benötigt zusätzlich zu quotes:write den Ziel-Scope: invoices:write für target \"invoice\", order-confirmations:write für target \"order-confirmation\" (Prüfung per hasScope mit der jeweiligen Konstante im Routen-Code). Fehlender Ziel-Scope → 403 mit requiredScopes.\n- invoice: wie convertQuoteToInvoice — Angebot wird atomar beansprucht (CONVERTED + convertedToInvoiceId), Rechnungsentwurf mit Platzhalternummer, Fälligkeit nach Zahlungsziel, Prozentrabatt → absoluter Rechnungsrabatt, Texte aus den Rechnungsvorlagen (nicht vom Angebot).\n- order-confirmation: wie createOrderConfirmationFromQuote — neue AB mit Positionen/Prozentrabatten des Angebots, notes = Abschlusstext des Angebots; das Angebot bleibt unverändert und weiter umwandelbar.\n- Bereits umgewandelt/abgelehnt → 409. Audit QUOTE_CONVERTED_VIA_API + INVOICE_CREATED_FROM_QUOTE_VIA_API bzw. ORDER_CONFIRMATION_CREATED_VIA_API. Webhook invoice.created (nur beim Ziel invoice).","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"target":{"type":"string","enum":["invoice","order-confirmation"],"description":"\"invoice\" oder \"order-confirmation\"."}},"required":["target"]}}}},"responses":{"201":{"description":"201 { data: { target, quoteId, invoice: {…} } } bzw. { target, quoteId, orderConfirmation } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["quotes:write","invoices:write"]}},"/api/v1/order-confirmations":{"get":{"operationId":"get_order_confirmations","tags":["order-confirmations"],"summary":"Auftragsbestätigungen auflisten","description":"**Scope:** `order-confirmations:read`\n\n**Antwort:** { data: OrderConfirmation[], pagination }\n\n- Sortierung issueDate desc. Ungültiger status → 400 mit allowedValues.","parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["DRAFT","SENT","CONFIRMED","IN_PROGRESS","COMPLETED","CANCELLED"]}},{"name":"customer_id","in":"query","required":false,"description":"Alias: `customerId`.","schema":{"type":"string"}},{"name":"search","in":"query","required":false,"description":"AB-Nummer und Kundenname.","schema":{"type":"string"}},{"name":"from","in":"query","required":false,"description":"Über issueDate.","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","required":false,"description":"Über issueDate.","schema":{"type":"string","format":"date"}},{"name":"category_id","in":"query","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"number","default":50}},{"name":"offset","in":"query","required":false,"schema":{"type":"number","default":0}}],"responses":{"200":{"description":"{ data: OrderConfirmation[], pagination }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/OrderConfirmation"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["order-confirmations:read"]},"post":{"operationId":"post_order_confirmations","tags":["order-confirmations"],"summary":"Auftragsbestätigung anlegen","description":"**Scope:** `order-confirmations:write`\n\n**Antwort:** 201 { data: OrderConfirmation }\n\n- Dokumentkategorie-Prüfung, Kunde muss zur Firma gehören (404), AB-Nummer aus dem Nummernkreis AB, Summen über calculateDocumentTotals (Prozent).\n- Kein Plan-Dokumentenlimit — Parität mit dem Dashboard, das dort ebenfalls nicht prüft (erst beim Umwandeln in eine Rechnung). Audit ORDER_CONFIRMATION_CREATED_VIA_API. Status immer DRAFT.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"customerId":{"type":"string"},"issueDate":{"type":"string","format":"date","description":"Standard: jetzt."},"deliveryDate":{"type":"string","format":"date","nullable":true},"validUntil":{"type":"string","format":"date","nullable":true},"documentCategoryId":{"type":"string","nullable":true},"reverseCharge":{"type":"boolean","default":false},"notes":{"type":"string","description":"Wird gedruckt."},"footer":{"type":"string"},"deliveryTerms":{"type":"string"},"paymentTerms":{"type":"string"},"quoteId":{"type":"string","description":"Optionaler Angebotsbezug. Alias: `quote_id`."},"lines":{"type":"array","items":{"type":"object"},"description":"Mindestens 1 Position, discount als Prozentwert."}},"required":["customerId","lines"]}}}},"responses":{"201":{"description":"201 { data: OrderConfirmation }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/OrderConfirmation"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["order-confirmations:write"]}},"/api/v1/order-confirmations/{id}":{"get":{"operationId":"get_order_confirmations_id","tags":["order-confirmations"],"summary":"Auftragsbestätigungsdetails lesen","description":"**Scope:** `order-confirmations:read`\n\n**Antwort:** { data: OrderConfirmation }\n\n- 404 \"Order confirmation not found\".","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"{ data: OrderConfirmation }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/OrderConfirmation"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["order-confirmations:read"]},"put":{"operationId":"put_order_confirmations_id","tags":["order-confirmations"],"summary":"Auftragsbestätigung im Entwurf ändern","description":"**Scope:** `order-confirmations:write`\n\n**Antwort:** { data: OrderConfirmation }\n\n- Nur Entwürfe (DRAFT), sonst 409. Audit ORDER_CONFIRMATION_UPDATED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"customerId":{"description":"Alle optional; null bei documentCategoryId/deliveryDate/validUntil löscht das Feld."},"documentCategoryId":{"description":"Alle optional; null bei documentCategoryId/deliveryDate/validUntil löscht das Feld."},"issueDate":{"description":"Alle optional; null bei documentCategoryId/deliveryDate/validUntil löscht das Feld."},"deliveryDate":{"description":"Alle optional; null bei documentCategoryId/deliveryDate/validUntil löscht das Feld."},"validUntil":{"description":"Alle optional; null bei documentCategoryId/deliveryDate/validUntil löscht das Feld."},"reverseCharge":{"description":"Alle optional; null bei documentCategoryId/deliveryDate/validUntil löscht das Feld."},"notes":{"description":"Alle optional; null bei documentCategoryId/deliveryDate/validUntil löscht das Feld."},"footer":{"description":"Alle optional; null bei documentCategoryId/deliveryDate/validUntil löscht das Feld."},"deliveryTerms":{"description":"Alle optional; null bei documentCategoryId/deliveryDate/validUntil löscht das Feld."},"paymentTerms":{"description":"Alle optional; null bei documentCategoryId/deliveryDate/validUntil löscht das Feld."},"lines":{"type":"array","items":{"type":"object"},"description":"Ersetzt alle Positionen."}}}}}},"responses":{"200":{"description":"{ data: OrderConfirmation }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/OrderConfirmation"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["order-confirmations:write"]},"delete":{"operationId":"delete_order_confirmations_id","tags":["order-confirmations"],"summary":"Auftragsbestätigungsentwurf löschen","description":"**Scope:** `order-confirmations:write`\n\n**Antwort:** { data: { id, deleted: true } }\n\n- Nur DRAFT, sonst 409. Audit ORDER_CONFIRMATION_DELETED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"{ data: { id, deleted: true } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["order-confirmations:write"]}},"/api/v1/order-confirmations/{id}/status":{"post":{"operationId":"post_order_confirmations_id_status","tags":["order-confirmations"],"summary":"Status einer Auftragsbestätigung setzen","description":"**Scope:** `order-confirmations:write`\n\n**Antwort:** 200 { data: OrderConfirmation }\n\n- Verlässt der Beleg dabei den Entwurf, zählt er aufs Dokumentenkontingent des Plans: bei erschöpftem Kontingent 403 (Stornieren eines Entwurfs bleibt möglich).\n- Keine Festschreibung, der Rechnungsverweis bleibt unberührt. Unbekannter Wert → 400 mit allowedValues. Audit ORDER_CONFIRMATION_STATUS_CHANGED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["DRAFT","SENT","CONFIRMED","IN_PROGRESS","COMPLETED","CANCELLED"],"description":"Jeder Wert erlaubt, keine Übergangsregeln."}},"required":["status"]}}}},"responses":{"200":{"description":"200 { data: OrderConfirmation }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/OrderConfirmation"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["order-confirmations:write"]}},"/api/v1/order-confirmations/{id}/send":{"post":{"operationId":"post_order_confirmations_id_send","tags":["order-confirmations"],"summary":"Auftragsbestätigung per E-Mail versenden","description":"**Scope:** `order-confirmations:write`\n\n**Antwort:** 200 { data: { orderConfirmationId, confirmationNumber, status, sentTo, cc, bcc } }\n\n- Verlässt der Beleg dabei den Entwurf, zählt er aufs Dokumentenkontingent des Plans: bei erschöpftem Kontingent 403.\n- PDF über lib/order-confirmation-pdf.ts, E-Mail-Typ ORDER_CONFIRMATION; DRAFT → SENT erst nach erfolgreichem Versand (bedingt).\n- lockDocument(\"ORDER_CONFIRMATION\") + Archivierung des versendeten Anhangs als Version 1. Kein Empfänger → 400; Versandfehler → 500. Audit ORDER_CONFIRMATION_SENT_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"recipientEmail":{"type":"string","description":"Alias: `recipient_email`. Standard: E-Mail des Kunden."},"cc":{"type":"string"},"bcc":{"type":"string"}}}}}},"responses":{"200":{"description":"200 { data: { orderConfirmationId, confirmationNumber, status, sentTo, cc, bcc } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["order-confirmations:write"]}},"/api/v1/order-confirmations/{id}/pdf":{"get":{"operationId":"get_order_confirmations_id_pdf","tags":["order-confirmations"],"summary":"Auftragsbestätigungs-PDF laden","description":"**Scope:** `order-confirmations:read`\n\n**Antwort:** application/pdf (Binär), Content-Disposition inline, X-PDF-Source.\n\n- Derselbe Renderer wie Dashboard/E-Mail. Fingerprint-Cache, Entwürfe ungecacht. Audit PDF_DOWNLOADED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"application/pdf (Binär), Content-Disposition inline, X-PDF-Source.","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["order-confirmations:read"]}},"/api/v1/order-confirmations/{id}/convert":{"post":{"operationId":"post_order_confirmations_id_convert","tags":["order-confirmations"],"summary":"Auftragsbestätigung in Rechnung oder Auftrag umwandeln","description":"**Scope:** `order-confirmations:write`, `invoices:write`\n\n**Antwort:** 201 { data: { target, orderConfirmationId, invoice: {…} } } bzw. { target, orderConfirmationId, order: {…} } }\n\n- Benötigt zusätzlich zu order-confirmations:write den Ziel-Scope: invoices:write für target \"invoice\", orders:write für target \"order\" (per hasScope-Prüfung im Routen-Code). Fehlender Ziel-Scope → 403.\n- invoice: wie convertOrderConfirmationToInvoice — Dokumentkategorie, mind. eine Position, Plan-Limit checkDocumentLimit (403); die AB wird atomar beansprucht (Status → COMPLETED), Rechnungsentwurf mit Platzhalternummer, Prozentrabatt → absoluter Rechnungsrabatt, notes = „Erstellt aus Auftragsbestätigung <Nr.>“.\n- order: über den gemeinsamen Auftragskern createOrderFromOrderConfirmation (lib/orders.ts) — Auftrag mit Nummer AU, Positionen/Prozentrabatten der AB, Verweise auf AB und Ursprungsangebot; die AB bleibt unverändert.\n- Bereits umgewandelt → 409. Audit ORDER_CONFIRMATION_CONVERTED_VIA_API + INVOICE_CREATED_FROM_ORDER_CONFIRMATION_VIA_API. Webhook invoice.created nach Commit (nur beim Ziel invoice).","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"target":{"type":"string","enum":["invoice","order"],"description":"\"invoice\" oder \"order\"."}},"required":["target"]}}}},"responses":{"201":{"description":"201 { data: { target, orderConfirmationId, invoice: {…} } } bzw. { target, orderConfirmationId, order: {…} } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["order-confirmations:write","invoices:write"]}},"/api/v1/delivery-notes":{"get":{"operationId":"get_delivery_notes","tags":["delivery-notes"],"summary":"Lieferscheine auflisten","description":"**Scope:** `delivery-notes:read`\n\n**Antwort:** 200 { data: DeliveryNote[], pagination }\n\n- Sortierung issueDate absteigend. Ungültiger status → 400 mit allowedValues.","parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["DRAFT","SENT","DELIVERED","CONVERTED"]}},{"name":"customer_id","in":"query","required":false,"description":"Alias: `customerId`.","schema":{"type":"string"}},{"name":"search","in":"query","required":false,"description":"Lieferscheinnummer oder Kundenname.","schema":{"type":"string"}},{"name":"from","in":"query","required":false,"description":"issueDate ≥/≤.","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","required":false,"description":"issueDate ≥/≤.","schema":{"type":"string","format":"date"}},{"name":"category_id","in":"query","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"number","default":50}},{"name":"offset","in":"query","required":false,"schema":{"type":"number","default":0}}],"responses":{"200":{"description":"200 { data: DeliveryNote[], pagination }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/DeliveryNote"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["delivery-notes:read"]},"post":{"operationId":"post_delivery_notes","tags":["delivery-notes"],"summary":"Lieferschein anlegen","description":"**Scope:** `delivery-notes:write`\n\n**Antwort:** 201 { data: DeliveryNote }\n\n- Vergibt die Nummer LS-{JAHR}-{0001}. Prüft das Dokumentenkontingent des Plans (→ 403 plan_no_access).\n- Kunde/Auftrag müssen zur Firma gehören (→ 404), unbekannte productId → 400. Audit DELIVERY_NOTE_CREATED_VIA_API. Status startet als DRAFT.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"customerId":{"type":"string"},"lines":{"type":"array","items":{"type":"object","properties":{"description":{"type":"string","description":"Pflicht außer wenn productId gesetzt ist."},"quantity":{"type":"number","default":1},"unit":{"type":"string","description":"Standard: Produkteinheit bzw. \"Stk\"."},"productId":{"type":"string","description":"Füllt Bezeichnung/Einheit wie der Produktwähler im Dashboard."}}},"description":"Mindestens 1; nur Beschreibung/Menge/Einheit, keine Preise."},"deliveryDate":{"type":"string","format":"date"},"shippingAddress":{"type":"string"},"notes":{"type":"string"},"documentCategoryId":{"type":"string","nullable":true},"orderId":{"type":"string","nullable":true,"description":"Verknüpft mit einem Auftrag."}},"required":["customerId","lines"]}}}},"responses":{"201":{"description":"201 { data: DeliveryNote }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/DeliveryNote"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["delivery-notes:write"]}},"/api/v1/delivery-notes/{id}":{"get":{"operationId":"get_delivery_notes_id","tags":["delivery-notes"],"summary":"Lieferscheindetails lesen","description":"**Scope:** `delivery-notes:read`\n\n**Antwort:** 200 { data: DeliveryNote }\n\n- 404, wenn der Lieferschein nicht zur Firma gehört.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"200 { data: DeliveryNote }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/DeliveryNote"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["delivery-notes:read"]},"put":{"operationId":"put_delivery_notes_id","tags":["delivery-notes"],"summary":"Lieferscheinentwurf ändern","description":"**Scope:** `delivery-notes:write`\n\n**Antwort:** 200 { data: DeliveryNote }\n\n- Nur Entwürfe (sonst 409). Das Dashboard hat keine Bearbeitungsseite für Lieferscheine — die Regel folgt der Löschregel. Audit DELIVERY_NOTE_UPDATED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"customerId":{"description":"Alle optional; null bei documentCategoryId/deliveryDate/orderId löscht das Feld."},"documentCategoryId":{"description":"Alle optional; null bei documentCategoryId/deliveryDate/orderId löscht das Feld."},"deliveryDate":{"description":"Alle optional; null bei documentCategoryId/deliveryDate/orderId löscht das Feld."},"shippingAddress":{"description":"Alle optional; null bei documentCategoryId/deliveryDate/orderId löscht das Feld."},"notes":{"description":"Alle optional; null bei documentCategoryId/deliveryDate/orderId löscht das Feld."},"orderId":{"description":"Alle optional; null bei documentCategoryId/deliveryDate/orderId löscht das Feld."},"lines":{"type":"array","items":{"type":"object"},"description":"Ersetzt alle Positionen, nummeriert sie neu ab 1."}}}}}},"responses":{"200":{"description":"200 { data: DeliveryNote }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/DeliveryNote"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["delivery-notes:write"]},"delete":{"operationId":"delete_delivery_notes_id","tags":["delivery-notes"],"summary":"Lieferscheinentwurf löschen","description":"**Scope:** `delivery-notes:write`\n\n**Antwort:** 200 { data: { id, deleted: true } }\n\n- Nur Entwürfe (sonst 409). Audit DELIVERY_NOTE_DELETED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"200 { data: { id, deleted: true } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["delivery-notes:write"]}},"/api/v1/delivery-notes/{id}/status":{"post":{"operationId":"post_delivery_notes_id_status","tags":["delivery-notes"],"summary":"Status eines Lieferscheins setzen","description":"**Scope:** `delivery-notes:write`\n\n**Antwort:** 200 { data: DeliveryNote }\n\n- Verlässt der Beleg dabei den Entwurf, zählt er aufs Dokumentenkontingent des Plans: bei erschöpftem Kontingent 403.\n- Audit DELIVERY_NOTE_STATUS_CHANGED_VIA_API mit { status: { old, new } }.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["DRAFT","SENT","DELIVERED"],"description":"CONVERTED ist nicht manuell setzbar → 400 mit Verweis auf /convert."}},"required":["status"]}}}},"responses":{"200":{"description":"200 { data: DeliveryNote }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/DeliveryNote"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["delivery-notes:write"]}},"/api/v1/delivery-notes/{id}/send":{"post":{"operationId":"post_delivery_notes_id_send","tags":["delivery-notes"],"summary":"Lieferschein per E-Mail versenden","description":"**Scope:** `delivery-notes:write`\n\n**Antwort:** 200 { data: { deliveryNoteId, status, sentTo, cc, bcc, sentAt } }\n\n- Verlässt der Beleg dabei den Entwurf, zählt er aufs Dokumentenkontingent des Plans: bei erschöpftem Kontingent 403.\n- PDF-Anhang (EmailType.DELIVERY), legt eine SentEmail-Zeile an. Danach DRAFT → SENT (nur solange nicht umgewandelt), lockDocument + archiveDistributedPdfAsInitialVersion legt v1 an.\n- Weder Kunden-E-Mail noch recipientEmail → 400. Fehlgeschlagener Versand → 500, kein Statuswechsel.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"recipientEmail":{"type":"string","description":"Standard: E-Mail des Kunden."},"cc":{"type":"string"},"bcc":{"type":"string"}}}}}},"responses":{"200":{"description":"200 { data: { deliveryNoteId, status, sentTo, cc, bcc, sentAt } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["delivery-notes:write"]}},"/api/v1/delivery-notes/{id}/pdf":{"get":{"operationId":"get_delivery_notes_id_pdf","tags":["delivery-notes"],"summary":"Lieferschein-PDF laden","description":"**Scope:** `delivery-notes:read`\n\n**Antwort:** 200 Binärdaten, Content-Type application/pdf, X-PDF-Source: s3-cache|generated.\n\n- Gleicher Renderer wie im Dashboard (lib/delivery-note-pdf.ts) inkl. Fingerprint-Cache; Entwürfe nie gecacht. Audit PDF_DOWNLOADED_VIA_API nur bei Treffer.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"200 Binärdaten, Content-Type application/pdf, X-PDF-Source: s3-cache|generated.","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["delivery-notes:read"]}},"/api/v1/delivery-notes/{id}/convert":{"post":{"operationId":"post_delivery_notes_id_convert","tags":["delivery-notes"],"summary":"Lieferschein in eine Rechnung umwandeln (Preise ergänzen)","description":"**Scope:** `delivery-notes:write`, `invoices:write`\n\n**Antwort:** 201 { data: { deliveryNoteId, invoiceId, invoice: { id, invoiceNumber, status, issueDate, dueDate, currency, netTotal, taxTotal, grossTotal, customer, lines[] } } }\n\n- Beansprucht den Lieferschein atomar (20s Budget): Status → CONVERTED, convertedToInvoiceId gesetzt; eine parallele Umwandlung erhält 409.\n- Rechnung entsteht als Entwurf mit Platzhalternummer ENTWURF-…; Texte aus den Rechnungsvorlagen, notes = „Erstellt aus Lieferschein {Nummer}“. Positionen ohne passendes linesPricing bekommen unitPrice 0 / taxRate 20.\n- Zwei Audit-Zeilen: DELIVERY_NOTE_CONVERTED_VIA_API (DeliveryNote) und INVOICE_CREATED_FROM_DELIVERY_NOTE_VIA_API (Invoice).","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"linesPricing":{"type":"array","items":{"type":"object","properties":{"position":{},"unitPrice":{},"taxRate":{}}},"description":"Mindestens 1; position bezieht sich auf DeliveryNoteLine.position."},"dueDate":{"type":"string","format":"date","description":"Standard: Zahlungsziel des Kunden, sonst der Firma, sonst 14 Tage."}},"required":["linesPricing"]}}}},"responses":{"201":{"description":"201 { data: { deliveryNoteId, invoiceId, invoice: { id, invoiceNumber, status, issueDate, dueDate, currency, netTotal, taxTotal, grossTotal, customer, lines[] } } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["delivery-notes:write","invoices:write"]}},"/api/v1/orders":{"get":{"operationId":"get_orders","tags":["orders"],"summary":"Aufträge auflisten","description":"**Scope:** `orders:read`\n\n**Antwort:** 200 { data: Order[], pagination }\n\n- Sortierung createdAt absteigend.","parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["DRAFT","ACTIVE","ON_HOLD","COMPLETED","CANCELLED"]}},{"name":"customer_id","in":"query","required":false,"description":"Alias: `customerId`.","schema":{"type":"string"}},{"name":"search","in":"query","required":false,"description":"Auftragsnummer, Bezeichnung oder Kundenname.","schema":{"type":"string"}},{"name":"category_id","in":"query","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"number","default":50}},{"name":"offset","in":"query","required":false,"schema":{"type":"number","default":0}}],"responses":{"200":{"description":"200 { data: Order[], pagination }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Order"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["orders:read"]},"post":{"operationId":"post_orders","tags":["orders"],"summary":"Auftrag anlegen","description":"**Scope:** `orders:write`\n\n**Antwort:** 201 { data: Order }\n\n- Vergibt die Nummer AU-{JAHR}-{0001}. totalBudget wird aus den Positionen über calculateDocumentTotals (Modus percent) als Nettosumme berechnet — Abzugspositionen mindern das Budget.\n- Kein Dokumentenkontingent (Aufträge zählen auch im Dashboard nicht dagegen). Audit ORDER_CREATED_VIA_API. Status startet als DRAFT, deliveredQty/invoicedQty bei 0.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"customerId":{"type":"string"},"name":{"type":"string","description":"Auftragsbezeichnung."},"lines":{"type":"array","items":{"type":"object","properties":{"description":{"type":"string","description":"Pflicht außer mit productId."},"quantity":{"type":"number","default":1},"unit":{"type":"string","description":"Standard: Produkteinheit bzw. \"Stk\"."},"unitPrice":{"type":"number","description":"Standard: Produktpreis bzw. 0."},"taxRate":{"type":"number","description":"Standard: Produktsteuersatz bzw. 20."},"discount":{"type":"number","default":0,"description":"Prozent, 0–100."},"lineType":{"type":"string","enum":["PRODUCT","SERVICE","DISCOUNT_PERCENT","DISCOUNT_ABSOLUTE","SHIPPING","ADVANCE_PAYMENT","CREDIT","TEXT","SUBTOTAL","PAGEBREAK"],"default":"PRODUCT"},"productId":{"type":"string"}}},"description":"Mindestens 1."},"description":{"type":"string"},"notes":{"type":"string"},"documentCategoryId":{"type":"string","nullable":true},"startDate":{"type":"string","format":"date","nullable":true},"endDate":{"type":"string","format":"date","nullable":true},"quoteId":{"type":"string","description":"Müssen zur Firma gehören (→ 404)"},"orderConfirmationId":{"type":"string","description":"Müssen zur Firma gehören (→ 404)"}},"required":["customerId","name","lines"]}}}},"responses":{"201":{"description":"201 { data: Order }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Order"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["orders:write"]}},"/api/v1/orders/{id}":{"get":{"operationId":"get_orders_id","tags":["orders"],"summary":"Auftragsdetails inkl. Statistiken lesen","description":"**Scope:** `orders:read`\n\n**Antwort:** 200 { data: Order & { stats: OrderStats, invoices: [...], deliveryNotes: [...] } }\n\n- stats entspricht getOrderStats der Detailseite: Abzugs-, Text- und Zwischensummenpositionen zählen nicht in die Mengenfortschritte.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"200 { data: Order & { stats: OrderStats, invoices: [...], deliveryNotes: [...] } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Order"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["orders:read"]},"put":{"operationId":"put_orders_id","tags":["orders"],"summary":"Auftrag ändern","description":"**Scope:** `orders:write`\n\n**Antwort:** 200 { data: Order }\n\n- Nur DRAFT und ACTIVE (sonst 409). Kunde ist nicht änderbar. Audit ORDER_UPDATED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"notes":{"type":"string"},"documentCategoryId":{"type":"string","nullable":true},"startDate":{"type":"string","format":"date","nullable":true},"endDate":{"type":"string","format":"date","nullable":true},"lines":{"type":"array","items":{"type":"object"},"description":"Ersetzt alle Positionen (deliveredQty/invoicedQty starten wieder bei 0); totalBudget wird neu berechnet."}}}}}},"responses":{"200":{"description":"200 { data: Order }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Order"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["orders:write"]},"delete":{"operationId":"delete_orders_id","tags":["orders"],"summary":"Auftragsentwurf löschen","description":"**Scope:** `orders:write`\n\n**Antwort:** 200 { data: { id, deleted: true } }\n\n- Nur Entwürfe (sonst 409). Audit ORDER_DELETED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"200 { data: { id, deleted: true } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["orders:write"]}},"/api/v1/orders/{id}/status":{"post":{"operationId":"post_orders_id_status","tags":["orders"],"summary":"Status eines Auftrags setzen","description":"**Scope:** `orders:write`\n\n**Antwort:** 200 { data: Order }\n\n- Verlässt der Beleg dabei den Entwurf, zählt er aufs Dokumentenkontingent des Plans: bei erschöpftem Kontingent 403 (Stornieren eines Entwurfs bleibt möglich).\n- Verlässt der Auftrag zum ersten Mal DRAFT, wird er festgeschrieben (lockDocument: lockedAt + v1-Snapshot). Idempotent — Re-Aktivierung aus ON_HOLD/COMPLETED erzeugt keine zweite v1.\n- Audit ORDER_STATUS_CHANGED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["DRAFT","ACTIVE","ON_HOLD","COMPLETED","CANCELLED"]}},"required":["status"]}}}},"responses":{"200":{"description":"200 { data: Order }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Order"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["orders:write"]}},"/api/v1/orders/{id}/lines/{lineId}":{"put":{"operationId":"put_orders_id_lines_lineId","tags":["orders"],"summary":"Gelieferte oder abgerechnete Menge einer Auftragsposition pflegen","description":"**Scope:** `orders:write`\n\n**Antwort:** 200 { data: { id, orderId, position, description, quantity, unit, unitPrice, taxRate, discount, discountMode: \"percent\", lineType, lineTotal, deliveredQty, invoicedQty } }\n\n- Entspricht updateOrderLineDeliveredQty/updateOrderLineInvoicedQty der Detailseite. Position muss zu diesem Auftrag und zur Firma gehören (sonst 404). Keine Menge gesendet → 400.\n- Audit ORDER_LINE_QUANTITY_UPDATED_VIA_API (auf dem Auftrag, mit lineId).","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}},{"name":"lineId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"deliveredQuantity":{"type":"number","description":"≥ 0."},"invoicedQuantity":{"type":"number","description":"≥ 0; mindestens eines von beiden ist Pflicht."}}}}}},"responses":{"200":{"description":"200 { data: { id, orderId, position, description, quantity, unit, unitPrice, taxRate, discount, discountMode: \"percent\", lineType, lineTotal, deliveredQty, invoicedQty } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["orders:write"]}},"/api/v1/orders/{id}/pdf":{"get":{"operationId":"get_orders_id_pdf","tags":["orders"],"summary":"Auftrags-PDF laden","description":"**Scope:** `orders:read`\n\n**Antwort:** 200 Binärdaten, Content-Type application/pdf, X-PDF-Source.\n\n- Gleicher Renderer wie im Dashboard (lib/order-pdf.ts) inkl. Fingerprint-Cache; zeigt Bestellt-/Geliefert-/Abgerechnet-Mengen und Fortschritt. Audit PDF_DOWNLOADED_VIA_API nur bei Treffer.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"200 Binärdaten, Content-Type application/pdf, X-PDF-Source.","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["orders:read"]}},"/api/v1/recurring-schedules":{"get":{"operationId":"get_recurring_schedules","tags":["recurring-schedules"],"summary":"Zeitpläne auflisten","description":"**Scope:** `recurring:read`\n\n**Antwort:** { data: Schedule[], meta: { total, limit, offset, hasMore } }\n\n- Ohne status und ohne include_archived=true werden archivierte Pläne ausgeblendet. Sortierung nextGenerationDate aufsteigend, dann createdAt absteigend.","parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["ACTIVE","PAUSED","ENDED","ARCHIVED"]}},{"name":"customer_id","in":"query","required":false,"schema":{"type":"string"}},{"name":"include_archived","in":"query","required":false,"schema":{"type":"boolean","default":false}},{"name":"limit","in":"query","required":false,"schema":{"type":"number","default":50}},{"name":"offset","in":"query","required":false,"schema":{"type":"number","default":0}}],"responses":{"200":{"description":"{ data: Schedule[], meta: { total, limit, offset, hasMore } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Schedule"}},"meta":{"$ref":"#/components/schemas/Pagination"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["recurring:read"]},"post":{"operationId":"post_recurring_schedules","tags":["recurring-schedules"],"summary":"Zeitplan für wiederkehrende Rechnungen anlegen","description":"**Scope:** `recurring:write`\n**Plan-Funktion:** `recurringInvoices`\n\n**Antwort:** 201 { data: Schedule }\n\n- Plan-Gate recurringInvoices (403). Prüft Kunde (404) und Dokumentkategorie (400). nextGenerationDate = Startdatum (falls Zukunft), sonst aus dem Rhythmus berechnet.\n- Audit RECURRING_SCHEDULE_CREATED_VIA_API.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"customerId":{"type":"string"},"frequency":{"type":"string","enum":["WEEKLY","BIWEEKLY","MONTHLY","QUARTERLY","SEMIANNUALLY","ANNUALLY"]},"startDate":{"type":"string","format":"date"},"lines":{"type":"array","items":{"type":"object"},"description":"Mindestens 1; je Position description (Pflicht), quantity, unitPrice, taxRate (Pflicht), discount (absolut), lineType, articleNumber."},"name":{"type":"string","description":"Standard: „{Kunde} - {Frequenz} - {Startdatum}“."},"description":{"type":"string"},"intervalMultiplier":{"type":"number","default":1,"description":"1–99."},"dayOfMonth":{"type":"number","description":"1–31."},"dayOfWeek":{"type":"number","description":"0–6, wird gespeichert, verschiebt das Datum nicht."},"useLastDayOfMonth":{"type":"boolean","default":false,"description":"Setzt dayOfMonth auf null."},"endDate":{"type":"string","format":"date"},"maxOccurrences":{"type":"number","description":"≥ 1."},"createAsDraft":{"type":"boolean","default":false},"autoSendEmail":{"type":"boolean","default":true},"notifyOnCreate":{"type":"boolean","default":false},"markAsPaid":{"type":"boolean","default":false},"emailCc":{"type":"string","description":"Kommagetrennte Adressen, werden normalisiert."},"emailBcc":{"type":"string","description":"Kommagetrennte Adressen, werden normalisiert."},"taxExemptionReason":{"type":"string","enum":["NONE","SMALL_BUSINESS","REVERSE_CHARGE_EU","REVERSE_CHARGE_EU_NO_VAT","THIRD_COUNTRY","INTRA_COMMUNITY","EXPORT_DELIVERY","REVERSE_CHARGE_13B","INTERNAL_SUPPLY","OTHER_EXEMPTION"],"default":"NONE"},"taxExemptionNote":{"type":"string"},"taxCountry":{"type":"string","format":"date","description":"Nur EU-Codes (AT, DE, …). Ein anderer Wert wird nicht übernommen: Dann gilt das aus den Steuersätzen erkannte Land, sonst keines. Die Antwort enthält den gespeicherten Wert. Standard: aus den Steuersätzen erkannt."},"headerText":{"type":"string"},"notes":{"type":"string"},"footer":{"type":"string"},"paymentTermDays":{"type":"number","description":"0–365."},"discountPercent":{"type":"number","description":"0–100."},"discountDays":{"type":"number","description":"0–365."},"orderNumber":{"type":"string"},"buyerReference":{"type":"string"},"externalId":{"type":"string"},"totalDiscountPercent":{"type":"number","description":"0–100."},"isOSS":{"type":"boolean"},"showServiceDateNotice":{"type":"boolean"},"documentCategoryId":{"type":"string","nullable":true}},"required":["customerId","frequency","startDate","lines"]}}}},"responses":{"201":{"description":"201 { data: Schedule }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Schedule"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["recurring:write"],"x-plan-feature":"recurringInvoices"}},"/api/v1/recurring-schedules/{id}":{"get":{"operationId":"get_recurring_schedules_id","tags":["recurring-schedules"],"summary":"Zeitplandetails inkl. erzeugter Rechnungen lesen","description":"**Scope:** `recurring:read`\n\n**Antwort:** { data: Schedule & { generatedInvoices: [{ id, invoiceNumber, status, issueDate, grossTotal }] } }\n\n- nextGenerationDate und generatedCount sind Teil von Schedule.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"{ data: Schedule & { generatedInvoices: [{ id, invoiceNumber, status, issueDate, grossTotal }] } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Schedule"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["recurring:read"]},"put":{"operationId":"put_recurring_schedules_id","tags":["recurring-schedules"],"summary":"Zeitplan ändern","description":"**Scope:** `recurring:write`\n\n**Antwort:** { data: Schedule }\n\n- Ändert sich der Rhythmus (frequency, intervalMultiplier, dayOfMonth, dayOfWeek, useLastDayOfMonth, startDate), wird nextGenerationDate neu berechnet. Kundenwechsel wird geprüft (404), Kategorie validiert (400).\n- taxCountry gilt wie beim Anlegen nur mit einem EU-Code; ein anderer Wert wird auf das aus den Steuersätzen erkannte Land zurückgeführt — ohne mitgesendete lines aus den gespeicherten Positionen des Zeitplans. Die Antwort enthält den gespeicherten Wert, die Korrektur ist dort also sichtbar.\n- Audit RECURRING_SCHEDULE_UPDATED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"customerId":{"type":"string"},"frequency":{"type":"string","enum":["WEEKLY","BIWEEKLY","MONTHLY","QUARTERLY","SEMIANNUALLY","ANNUALLY"]},"startDate":{"type":"string","format":"date"},"lines":{"type":"array","items":{"type":"object"},"description":"Mindestens 1; je Position description (Pflicht), quantity, unitPrice, taxRate (Pflicht), discount (absolut), lineType, articleNumber."},"name":{"type":"string","description":"Standard: „{Kunde} - {Frequenz} - {Startdatum}“."},"description":{"type":"string"},"intervalMultiplier":{"type":"number","default":1,"description":"1–99."},"dayOfMonth":{"type":"number","description":"1–31."},"dayOfWeek":{"type":"number","description":"0–6, wird gespeichert, verschiebt das Datum nicht."},"useLastDayOfMonth":{"type":"boolean","default":false,"description":"Setzt dayOfMonth auf null."},"endDate":{"type":"string","format":"date"},"maxOccurrences":{"type":"number","description":"≥ 1."},"createAsDraft":{"type":"boolean","default":false},"autoSendEmail":{"type":"boolean","default":true},"notifyOnCreate":{"type":"boolean","default":false},"markAsPaid":{"type":"boolean","default":false},"emailCc":{"type":"string","description":"Kommagetrennte Adressen, werden normalisiert."},"emailBcc":{"type":"string","description":"Kommagetrennte Adressen, werden normalisiert."},"taxExemptionReason":{"type":"string","enum":["NONE","SMALL_BUSINESS","REVERSE_CHARGE_EU","REVERSE_CHARGE_EU_NO_VAT","THIRD_COUNTRY","INTRA_COMMUNITY","EXPORT_DELIVERY","REVERSE_CHARGE_13B","INTERNAL_SUPPLY","OTHER_EXEMPTION"],"default":"NONE"},"taxExemptionNote":{"type":"string"},"taxCountry":{"type":"string","format":"date","description":"Nur EU-Codes (AT, DE, …). Ein anderer Wert wird nicht übernommen: Dann gilt das aus den Steuersätzen erkannte Land, sonst keines. Die Antwort enthält den gespeicherten Wert. Standard: aus den Steuersätzen erkannt."},"headerText":{"type":"string"},"notes":{"type":"string"},"footer":{"type":"string"},"paymentTermDays":{"type":"number","description":"0–365."},"discountPercent":{"type":"number","description":"0–100."},"discountDays":{"type":"number","description":"0–365."},"orderNumber":{"type":"string"},"buyerReference":{"type":"string"},"externalId":{"type":"string"},"totalDiscountPercent":{"type":"number","description":"0–100."},"isOSS":{"type":"boolean"},"showServiceDateNotice":{"type":"boolean"},"documentCategoryId":{"type":"string","nullable":true}},"description":"(alle Felder von POST): Alle optional, nur gesendete Felder ändern sich. null löscht endDate, maxOccurrences, dayOfMonth, dayOfWeek, paymentTermDays, discountDays, discountPercent, totalDiscountPercent. status wird hier nicht akzeptiert."}}}},"responses":{"200":{"description":"{ data: Schedule }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Schedule"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["recurring:write"]},"delete":{"operationId":"delete_recurring_schedules_id","tags":["recurring-schedules"],"summary":"Zeitplan löschen","description":"**Scope:** `recurring:write`\n\n**Antwort:** { data: { id, deleted: true } }\n\n- Nur wenn noch keine Rechnung erzeugt wurde; sonst 409 mit archiveEndpoint. Audit RECURRING_SCHEDULE_DELETED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"{ data: { id, deleted: true } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["recurring:write"]}},"/api/v1/recurring-schedules/{id}/status":{"post":{"operationId":"post_recurring_schedules_id_status","tags":["recurring-schedules"],"summary":"Status eines Zeitplans setzen","description":"**Scope:** `recurring:write`\n\n**Antwort:** 200 { data: Schedule }\n\n- ENDED → ACTIVE ist gesperrt (409). Beim Aktivieren gewinnt ein zukünftiges Startdatum, aus PAUSED wird ab heute neu gerechnet; ENDED löscht nextGenerationDate. ARCHIVED läuft über /archive.\n- Audit RECURRING_SCHEDULE_STATUS_CHANGED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["ACTIVE","PAUSED","ENDED"]}},"required":["status"]}}}},"responses":{"200":{"description":"200 { data: Schedule }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Schedule"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["recurring:write"]}},"/api/v1/recurring-schedules/{id}/archive":{"post":{"operationId":"post_recurring_schedules_id_archive","tags":["recurring-schedules"],"summary":"Zeitplan archivieren","description":"**Scope:** `recurring:write`\n\n**Antwort:** 200 { data: Schedule }\n\n- Setzt ARCHIVED und löscht nextGenerationDate. Bereits archiviert → 409. Audit RECURRING_SCHEDULE_ARCHIVED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"200 { data: Schedule }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Schedule"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["recurring:write"]}},"/api/v1/recurring-schedules/{id}/restore":{"post":{"operationId":"post_recurring_schedules_id_restore","tags":["recurring-schedules"],"summary":"Archivierten Zeitplan wiederherstellen","description":"**Scope:** `recurring:write`\n\n**Antwort:** 200 { data: Schedule }\n\n- Nur aus ARCHIVED (sonst 409), immer nach PAUSED; nextGenerationDate bleibt leer, bis aktiviert wird. Audit RECURRING_SCHEDULE_RESTORED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"200 { data: Schedule }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Schedule"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["recurring:write"]}},"/api/v1/recurring-schedules/{id}/generate":{"post":{"operationId":"post_recurring_schedules_id_generate","tags":["recurring-schedules"],"summary":"Rechnung aus dem Zeitplan sofort erzeugen","description":"**Scope:** `recurring:write`\n**Plan-Funktion:** `recurringInvoices`\n\n**Antwort:** 201 { data: { id, invoiceNumber, status, issueDate, dueDate, currency, netTotal, taxTotal, grossTotal, customerId }, schedule: { id, status, generatedCount, lastGeneratedAt, nextGenerationDate }, warnings?: string[] }\n\n- Entsteht die Rechnung nicht als Entwurf, zählt sie sofort aufs Dokumentenkontingent: bei erschöpftem Kontingent 403 plan_limit_reached, ohne Nummer zu verbrauchen.\n- Plan-Gate recurringInvoices (403). Erzeugt über denselben Fachkern wie das Dashboard: Nummernvergabe (Entwürfe ENTWURF-…), Status je nach createAsDraft/autoSendEmail/markAsPaid, E-Mail-Versand mit PDF bei autoSendEmail (inkl. emailCc/emailBcc; scheitert der Versand, steht die Meldung in warnings), In-App-Benachrichtigung RECURRING_GENERATED bei notifyOnCreate.\n- Erreicht der Plan maxOccurrences, wird er auf ENDED gesetzt. 409 bei: Plan nicht ACTIVE (NOT_ACTIVE), Startdatum in der Zukunft ohne force (BEFORE_START_DATE), Maximum erreicht (MAX_OCCURRENCES), Plan ohne gültige Positionen.\n- Audit RECURRING_SCHEDULE_GENERATED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"force":{"type":"boolean","default":false,"description":"Erzeugt auch, wenn das Startdatum noch in der Zukunft liegt."}}}}}},"responses":{"201":{"description":"201 { data: { id, invoiceNumber, status, issueDate, dueDate, currency, netTotal, taxTotal, grossTotal, customerId }, schedule: { id, status, generatedCount, lastGeneratedAt, nextGenerationDate }, warnings?: string[] }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["recurring:write"],"x-plan-feature":"recurringInvoices"}},"/api/v1/recurring-schedules/{id}/recalculate":{"post":{"operationId":"post_recurring_schedules_id_recalculate","tags":["recurring-schedules"],"summary":"Nächstes Generierungsdatum neu berechnen","description":"**Scope:** `recurring:write`\n\n**Antwort:** 200 { data: Schedule }\n\n- Setzt nextGenerationDate neu (zukünftiges Startdatum gewinnt, sonst ab heute). ENDED/ARCHIVED → 409. Audit RECURRING_SCHEDULE_RECALCULATED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"200 { data: Schedule }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Schedule"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["recurring:write"]}},"/api/v1/documents":{"get":{"operationId":"get_documents","tags":["documents"],"summary":"Belege auflisten","description":"**Scope:** `documents:read`\n\n**Antwort:** { data: Document[], meta: { total, limit, offset, hasMore } }\n\n- Sortierung createdAt absteigend. Dateiinhalte werden nie mitgeliefert.","parameters":[{"name":"type","in":"query","required":false,"schema":{"type":"string","enum":["INVOICE_PDF","RECEIPT","CONTRACT","OFFER","DELIVERY_NOTE","CORRESPONDENCE","TAX_DOCUMENT","OTHER"]}},{"name":"archived","in":"query","required":false,"description":"Standard: false.","schema":{"type":"string"}},{"name":"invoice_id","in":"query","required":false,"schema":{"type":"string"}},{"name":"customer_id","in":"query","required":false,"schema":{"type":"string"}},{"name":"category_id","in":"query","required":false,"schema":{"type":"string"}},{"name":"search","in":"query","required":false,"description":"Name, Beschreibung, Tag.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"number","default":50}},{"name":"offset","in":"query","required":false,"schema":{"type":"number","default":0}}],"responses":{"200":{"description":"{ data: Document[], meta: { total, limit, offset, hasMore } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Document"}},"meta":{"$ref":"#/components/schemas/Pagination"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["documents:read"]},"post":{"operationId":"post_documents","tags":["documents"],"summary":"Beleg direkt hochladen (Base64)","description":"**Scope:** `documents:write`\n\n**Antwort:** 201 { data: Document }\n\n- 400 FILE_TOO_LARGE über 20 MB, 400 MIME_NOT_ALLOWED außerhalb der Allow-List, 403 STORAGE_LIMIT bei erschöpftem Speicherkontingent des Plans.\n- Ablage: S3 wenn STORAGE_PROVIDER=s3, sonst Base64 in der Datenbank (502 STORAGE_FAILED bei S3-Fehler). Rechnung/Kunde/Kategorie werden geprüft (404/400). Audit DOCUMENT_CREATED_VIA_API.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"mimeType":{"type":"string","description":"Allow-List: PDF, PNG, JPEG, GIF, WebP, DOC(X), XLS(X), TXT, CSV."},"content":{"type":"string","format":"byte","description":"Mit oder ohne data:-Präfix; max. 20 MB."},"type":{"type":"string","enum":["INVOICE_PDF","RECEIPT","CONTRACT","OFFER","DELIVERY_NOTE","CORRESPONDENCE","TAX_DOCUMENT","OTHER"],"default":"OTHER"},"description":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"invoiceId":{"type":"string"},"customerId":{"type":"string"},"documentCategoryId":{"type":"string"}},"required":["name","mimeType","content"]}}}},"responses":{"201":{"description":"201 { data: Document }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Document"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["documents:write"]}},"/api/v1/documents/upload-url":{"post":{"operationId":"post_documents_upload_url","tags":["documents"],"summary":"Presigned S3-Upload-URL anfordern","description":"**Scope:** `documents:write`\n\n**Antwort:** 200 { data: { uploadUrl, key, method: \"PUT\", headers: { \"Content-Type\" }, expiresAt } }\n\n- 403 STORAGE_LIMIT, wenn die Datei das Speicherkontingent des Plans überschreiten würde.\n- Nur mit S3 als Ablage, sonst 400 S3_REQUIRED (Hinweis auf POST /documents). URL 5 Minuten gültig. Legt noch keinen Beleg an.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"fileName":{"type":"string"},"name":{"type":"string"},"mimeType":{"type":"string"},"size":{"type":"number","description":"Bytes > 0, max. 100 MB."},"type":{"type":"string","enum":["INVOICE_PDF","RECEIPT","CONTRACT","OFFER","DELIVERY_NOTE","CORRESPONDENCE","TAX_DOCUMENT","OTHER"],"default":"OTHER"}},"required":["mimeType","size"]}}}},"responses":{"200":{"description":"200 { data: { uploadUrl, key, method: \"PUT\", headers: { \"Content-Type\" }, expiresAt } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["documents:write"]}},"/api/v1/documents/confirm":{"post":{"operationId":"post_documents_confirm","tags":["documents"],"summary":"Direkt-Upload abschließen und Beleg anlegen","description":"**Scope:** `documents:write`\n\n**Antwort:** 201 { data: Document }\n\n- Fürs Speicherkontingent zählt die Größe des Objekts in S3, nicht die size-Angabe (diese wird überschrieben). Über dem Kontingent → 403 STORAGE_LIMIT, das hochgeladene Objekt wird gelöscht; ohne hochgeladenes Objekt → 400 UPLOAD_NOT_FOUND.\n- Entspricht confirmDirectUpload im Dashboard. Ohne S3 → 400 S3_REQUIRED. Abweichung von der ursprünglichen Planung: kein {id} im Pfad, da vor der Bestätigung noch kein Beleg existiert.\n- Audit DOCUMENT_CREATED_VIA_API.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"key":{"type":"string","description":"Aus /upload-url, muss mit der Firmen-Id beginnen (sonst 400 INVALID_STORAGE_KEY)"},"name":{"type":"string"},"mimeType":{"type":"string"},"size":{"type":"number"},"type":{"type":"string","enum":["INVOICE_PDF","RECEIPT","CONTRACT","OFFER","DELIVERY_NOTE","CORRESPONDENCE","TAX_DOCUMENT","OTHER"]},"description":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"invoiceId":{"type":"string"},"customerId":{"type":"string"},"documentCategoryId":{"type":"string"}},"required":["key","name","mimeType","size"]}}}},"responses":{"201":{"description":"201 { data: Document }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Document"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["documents:write"]}},"/api/v1/documents/{id}":{"get":{"operationId":"get_documents_id","tags":["documents"],"summary":"Belegmetadaten lesen","description":"**Scope:** `documents:read`\n\n**Antwort:** { data: Document }\n\n- Nur Metadaten.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"{ data: Document }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Document"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["documents:read"]},"put":{"operationId":"put_documents_id","tags":["documents"],"summary":"Belegmetadaten ändern","description":"**Scope:** `documents:write`\n\n**Antwort:** { data: Document }\n\n- Nur Metadaten, die Datei bleibt unverändert. Bezüge werden gegen die Firma geprüft (404). Audit DOCUMENT_UPDATED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Nicht leer."},"description":{"type":"string"},"type":{"type":"string","enum":["INVOICE_PDF","RECEIPT","CONTRACT","OFFER","DELIVERY_NOTE","CORRESPONDENCE","TAX_DOCUMENT","OTHER"]},"tags":{"type":"array","items":{"type":"string"}},"invoiceId":{"type":"string","nullable":true},"customerId":{"type":"string","nullable":true},"documentCategoryId":{"type":"string","nullable":true}}}}}},"responses":{"200":{"description":"{ data: Document }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Document"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["documents:write"]},"delete":{"operationId":"delete_documents_id","tags":["documents"],"summary":"Beleg löschen","description":"**Scope:** `documents:write`\n\n**Antwort:** { data: { id, deleted: true } }\n\n- Löscht auch die Datei in S3 (ein S3-Fehler verhindert das Löschen des Datensatzes nicht). Audit DOCUMENT_DELETED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"{ data: { id, deleted: true } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["documents:write"]}},"/api/v1/documents/{id}/download":{"get":{"operationId":"get_documents_id_download","tags":["documents"],"summary":"Beleginhalt herunterladen","description":"**Scope:** `documents:read`\n\n**Antwort:** S3: { data: { id, name, mimeType, url, expiresAt } }. Lokal: Binärdaten mit Content-Disposition attachment.\n\n- Vorsignierte S3-URL, 1 Stunde gültig. Fehlende Daten → 404 (DATA_MISSING/STORAGE_FAILED).","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"S3: { data: { id, name, mimeType, url, expiresAt } }. Lokal: Binärdaten mit Content-Disposition attachment.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}},"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["documents:read"]}},"/api/v1/documents/{id}/archive":{"post":{"operationId":"post_documents_id_archive","tags":["documents"],"summary":"Beleg archivieren","description":"**Scope:** `documents:write`\n\n**Antwort:** 200 { data: Document }\n\n- Setzt isArchived=true, archivedAt=jetzt. Bereits archiviert → 409. Audit DOCUMENT_ARCHIVED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"200 { data: Document }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Document"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["documents:write"]}},"/api/v1/documents/{id}/unarchive":{"post":{"operationId":"post_documents_id_unarchive","tags":["documents"],"summary":"Beleg aus dem Archiv zurückholen","description":"**Scope:** `documents:write`\n\n**Antwort:** 200 { data: Document }\n\n- Setzt isArchived=false, archivedAt=null. Nicht archiviert → 409. Audit DOCUMENT_ARCHIVED_VIA_API (dieselbe Aktion, die Richtung steht in changes.isArchived).","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"200 { data: Document }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Document"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["documents:write"]}},"/api/v1/exports":{"get":{"operationId":"get_exports","tags":["exports"],"summary":"Buchhaltungsexport erzeugen (CSV, DATEV, BMD, UStVA)","description":"**Scope:** `exports:read`\n\n**Antwort:** Datei als Content-Disposition attachment. csv/ust: text/csv. datev: text/csv (Buchungsstapel). bmd: application/zip (buchungen.csv, personenkonten.csv, hinweise.txt). Mit Accept: application/json liefert csv stattdessen { csv, filename, summary, warnings[] }.\n\n- Identische Dateien wie /dashboard/exports; gleiche Belegauswahl (taxRelevantInvoiceConditions: keine Entwürfe, keine Proformas, stornierte Rechnungen nur mit Storno-Gutschrift).\n- Plan-Gate datevExport für type=datev/bmd (403); csv und ust sind in jedem Plan enthalten. Ungültiger type → 400 mit allowedValues. Kein AuditLog (wie im Dashboard).","parameters":[{"name":"type","in":"query","required":false,"schema":{"type":"string","enum":["csv","datev","bmd","ust"],"default":"csv"}},{"name":"year","in":"query","required":false,"description":"Standard: laufendes Jahr.","schema":{"type":"number"}},{"name":"month","in":"query","required":false,"description":"0–11.","schema":{"type":"number"}},{"name":"quarter","in":"query","required":false,"description":"1–4; ohne month/quarter gilt das ganze Jahr.","schema":{"type":"number"}},{"name":"category_id","in":"query","required":false,"description":"auch \"all\"/\"uncategorized\".","schema":{"type":"string"}},{"name":"category","in":"query","required":false,"description":"auch \"all\"/\"uncategorized\".","schema":{"type":"string"}}],"responses":{"200":{"description":"Datei als Content-Disposition attachment. csv/ust: text/csv. datev: text/csv (Buchungsstapel). bmd: application/zip (buchungen.csv, personenkonten.csv, hinweise.txt). Mit Accept: application/json liefert csv stattdessen { csv, filename, summary, warnings[] }.","content":{"application/json":{"schema":{"type":"object"}},"application/zip":{"schema":{"type":"string","format":"binary"}},"text/csv":{"schema":{"type":"string","format":"binary"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["exports:read"]}},"/api/v1/exports/sepa":{"post":{"operationId":"post_exports_sepa","tags":["exports"],"summary":"SEPA-Lastschriftdatei (PAIN.008) erzeugen","description":"**Scope:** `exports:read`\n\n**Antwort:** 200 Standard: application/xml, Content-Disposition attachment, Header X-Sepa-Message-Id/-Transaction-Count/-Warnings. Mit Accept: application/json: { xml, filename, messageId, transactionCount, totalAmount, warnings[] }.\n\n- Firma braucht sepaCreditorId (400 MISSING_CREDITOR_ID) und iban (400 MISSING_IBAN); je Kunde werden IBAN, Mandatsreferenz, -datum und Status ACTIVE verlangt — fehlt etwas, wird der Kunde übersprungen und als Warnung gemeldet.\n- Bleibt keine Lastschrift übrig: 400 NO_VALID_TRANSACTIONS mit details. Keine der Rechnungen gefunden → 404. Audit SEPA_EXPORT_VIA_API.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"invoiceIds":{"type":"array","items":{"type":"string"},"description":"Mindestens 1."},"requestedCollectionDate":{"type":"string","format":"date"},"mandateType":{"type":"string","enum":["CORE","B2B"],"default":"CORE"}},"required":["invoiceIds","requestedCollectionDate"]}}}},"responses":{"200":{"description":"200 Standard: application/xml, Content-Disposition attachment, Header X-Sepa-Message-Id/-Transaction-Count/-Warnings. Mit Accept: application/json: { xml, filename, messageId, transactionCount, totalAmount, warnings[] }.","content":{"application/json":{"schema":{"type":"object"}},"application/xml":{"schema":{"type":"string","format":"binary"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["exports:read"]}},"/api/v1/exports/customers":{"get":{"operationId":"get_exports_customers","tags":["exports"],"summary":"Kunden als CSV exportieren","description":"**Scope:** `exports:read`\n\n**Antwort:** CSV mit BOM, Content-Disposition attachment, Header X-Export-Customer-Count.\n\n- Gleiche 43 Spalten (deutsche Kopfzeile) und Formel-Neutralisierung wie der Dashboard-Export. Audit CSV_EXPORT_CUSTOMERS (zusätzlich apiKeyId in changes).","parameters":[{"name":"include_archived","in":"query","required":false,"description":"Alias: `includeArchived`.","schema":{"type":"boolean","default":false}},{"name":"format","in":"query","required":false,"schema":{"type":"string","enum":["comma","semicolon"],"default":"semicolon"}}],"responses":{"200":{"description":"CSV mit BOM, Content-Disposition attachment, Header X-Export-Customer-Count.","content":{"text/csv":{"schema":{"type":"string","format":"binary"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["exports:read"]}},"/api/v1/company":{"get":{"operationId":"get_company","tags":["company"],"summary":"Firmenstammdaten, Nummernkreise und Plan-Nutzung lesen","description":"**Scope:** `company:read`\n\n**Antwort:** { data: { id, name, address, contact, legal, tax, bank, currency, paymentTerms, dunning, branding, numberRanges, plan: { plan, subscriptionPlan, subscriptionExpiresAt, trialEndsAt, features, limits }, usage: { documents, customers, emails, storage, apiCalls, users } } }\n\n- Read-only, keine Seiteneffekte.\n- Bewusst NICHT enthalten: SMTP-Zugangsdaten, E-Mail-Domain-/DKIM-Einstellungen, Stripe-Ids, finAPI-Verknüpfung, API-Schlüssel, BMD-Kontenkonfiguration, Admin-Plan-Übersteuerung, Logo-Rohablage.","responses":{"200":{"description":"{ data: { id, name, address, contact, legal, tax, bank, currency, paymentTerms, dunning, branding, numberRanges, plan: { plan, subscriptionPlan, subscriptionExpiresAt, trialEndsAt, features, limits }, usage: { documents, customers, emails, storage, apiCalls, users } } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["company:read"]}},"/api/v1/webhooks":{"get":{"operationId":"get_webhooks","tags":["webhooks"],"summary":"Webhook-Endpunkte auflisten","description":"**Scope:** `webhooks:read`\n**Plan-Funktion:** `webhooks`\n\n**Antwort:** { data: [{ id, name, url, events[], is_active, max_retries, retry_delay_ms, rate_limit_per_min, total_deliveries, created_at, updated_at }] }\n\n- Enterprise-Plan-Gate (Feature \"webhooks\"). Liefert nie das secret.","responses":{"200":{"description":"{ data: [{ id, name, url, events[], is_active, max_retries, retry_delay_ms, rate_limit_per_min, total_deliveries, created_at, updated_at }] }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"type":"object"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["webhooks:read"],"x-plan-feature":"webhooks"},"post":{"operationId":"post_webhooks","tags":["webhooks"],"summary":"Webhook-Endpunkt anlegen","description":"**Scope:** `webhooks:write`\n**Plan-Funktion:** `webhooks`\n\n**Antwort:** 201 { data: { id, name, url, secret, events[], is_active, max_retries, retry_delay_ms, created_at } } — secret wird nur hier zurückgegeben\n\n- Audit WEBHOOK_ENDPOINT_CREATED. 400 fehlender name/url/events, ungültiger Event-Typ, oder eine URL, die den Public-HTTPS-Egress-Check nicht besteht (privat/lokal/mit Zugangsdaten/kein HTTPS).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"url":{"type":"string","description":"Muss zu einem öffentlichen HTTPS-Host auflösen (assertPublicHttpUrl)"},"events":{"type":"array","items":{"type":"string"},"description":"Nicht leer, jeder Wert gegen WebhookEventType geprüft (Groß-/Kleinschreibung und „-“ egal)"},"secret":{"type":"string","description":"Standard: generiert als whsec_<48 hex>."},"max_retries":{"type":"number","default":3,"description":"Alias: `maxRetries`."},"retry_delay_ms":{"type":"number","default":1000,"description":"Alias: `retryDelayMs`."},"rate_limit_per_min":{"type":"number","nullable":true,"description":"Alias: `rateLimitPerMin`. Standard: null (unbegrenzt)."},"headers":{"type":"object","description":"Wird bei jeder Zustellung mitgesendet."},"metadata":{"type":"object"}},"required":["name","url","events"]}}}},"responses":{"201":{"description":"201 { data: { id, name, url, secret, events[], is_active, max_retries, retry_delay_ms, created_at } } — secret wird nur hier zurückgegeben","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["webhooks:write"],"x-plan-feature":"webhooks"}},"/api/v1/webhooks/{id}":{"get":{"operationId":"get_webhooks_id","tags":["webhooks"],"summary":"Webhook-Endpunkt inkl. letzter Zustellungen lesen","description":"**Scope:** `webhooks:read`\n**Plan-Funktion:** `webhooks`\n\n**Antwort:** { data: { id, name, url, events[], is_active, max_retries, retry_delay_ms, rate_limit_per_min, headers, metadata, total_deliveries, recent_deliveries: [...] (letzte 10), created_at, updated_at } }\n\n- 404, wenn nicht gefunden oder fremde Firma.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"{ data: { id, name, url, events[], is_active, max_retries, retry_delay_ms, rate_limit_per_min, headers, metadata, total_deliveries, recent_deliveries: [...] (letzte 10), created_at, updated_at } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["webhooks:read"],"x-plan-feature":"webhooks"},"put":{"operationId":"put_webhooks_id","tags":["webhooks"],"summary":"Webhook-Endpunkt ändern","description":"**Scope:** `webhooks:write`\n**Plan-Funktion:** `webhooks`\n\n**Antwort:** 200 { data: { id, name, url, events[], is_active, max_retries, retry_delay_ms, rate_limit_per_min, updated_at } } — rate_limit_per_min ist neu in der Antwort\n\n- 404 wenn nicht gefunden. 400 bei leerer/ungültiger url, nicht-array events oder ungültigem Event-Typ.\n- Audit WEBHOOK_ENDPOINT_UPDATED mit changes = { apiKeyId, <feld>: { old, new } } für jedes tatsächlich geänderte Feld. headers-Änderungen werden nur als { old_keys, new_keys } geloggt (Werte nie im Audit-Log, da sie Secrets wie Authorization-Token enthalten können).","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"description":"Alle optional/partiell."},"url":{"description":"Alle optional/partiell."},"events":{"description":"Alle optional/partiell."},"is_active":{"type":"boolean","description":"Alias: `isActive`."},"max_retries":{"type":"number","description":"Alias: `maxRetries`."},"retry_delay_ms":{"type":"number","description":"Alias: `retryDelayMs`."},"rate_limit_per_min":{"type":"number","nullable":true,"description":"Alias: `rateLimitPerMin`."},"headers":{"type":"object"},"metadata":{"type":"object"}}}}}},"responses":{"200":{"description":"200 { data: { id, name, url, events[], is_active, max_retries, retry_delay_ms, rate_limit_per_min, updated_at } } — rate_limit_per_min ist neu in der Antwort","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["webhooks:write"],"x-plan-feature":"webhooks"},"delete":{"operationId":"delete_webhooks_id","tags":["webhooks"],"summary":"Webhook-Endpunkt löschen","description":"**Scope:** `webhooks:write`\n**Plan-Funktion:** `webhooks`\n\n**Antwort:** 200 { success: true }\n\n- Hard Delete (kaskadiert WebhookDelivery-Zeilen per FK). Audit WEBHOOK_ENDPOINT_DELETED. 404 wenn nicht gefunden.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"200 { success: true }","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["webhooks:write"],"x-plan-feature":"webhooks"}},"/api/v1/webhooks/{id}/deliveries":{"get":{"operationId":"get_webhooks_id_deliveries","tags":["webhooks"],"summary":"Zustellversuche eines Webhook-Endpunkts auflisten","description":"**Scope:** `webhooks:read`\n**Plan-Funktion:** `webhooks`\n\n**Antwort:** { data: [{ id, event_type, event_id, status, attempts, last_attempt_at, next_retry_at, response_status, response_body, response_time_ms, error_message, created_at, completed_at }], pagination: { total, limit, offset, has_more } }\n\n- 404 wenn Endpunkt nicht gefunden. Ungültiges status/event_type → 400 mit allowedValues (vorher leeres Ergebnis bzw. möglicher 500).","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["PENDING","SUCCESS","FAILED","RETRYING"]}},{"name":"event_type","in":"query","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"number","default":50}},{"name":"offset","in":"query","required":false,"schema":{"type":"number","default":0}}],"responses":{"200":{"description":"{ data: [{ id, event_type, event_id, status, attempts, last_attempt_at, next_retry_at, response_status, response_body, response_time_ms, error_message, created_at, completed_at }], pagination: { total, limit, offset, has_more } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"type":"object"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["webhooks:read"],"x-plan-feature":"webhooks"}},"/api/v1/blog/posts":{"get":{"operationId":"get_blog_posts","tags":["blog"],"summary":"Blogposts auflisten","description":"**Scope:** `blog:read`\n\n**Antwort:** { posts: BlogPost[], pagination: { total, limit, offset } }","responses":{"200":{"description":"{ posts: BlogPost[], pagination: { total, limit, offset } }","content":{"application/json":{"schema":{"type":"object","required":["posts"],"properties":{"posts":{"type":"array","items":{"$ref":"#/components/schemas/BlogPost"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:read"]},"post":{"operationId":"post_blog_posts","tags":["blog"],"summary":"Blogpost anlegen","description":"**Scope:** `blog:write`\n\n**Antwort:** 201 BlogPost (ohne data-Hülle)\n\n- status: \"PUBLISHED\" beim Anlegen erfordert zusätzlich blog:publish.","responses":{"201":{"description":"201 BlogPost (ohne data-Hülle)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlogPost"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:write"]}},"/api/v1/blog/posts/{id}":{"get":{"operationId":"get_blog_posts_id","tags":["blog"],"summary":"Blogpost lesen","description":"**Scope:** `blog:read`\n\n**Antwort:** BlogPost (ohne data-Hülle)","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"BlogPost (ohne data-Hülle)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlogPost"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:read"]},"patch":{"operationId":"patch_blog_posts_id","tags":["blog"],"summary":"Blogpost ändern","description":"**Scope:** `blog:write`\n\n**Antwort:** BlogPost (ohne data-Hülle)\n\n- Statuswechsel auf/von PUBLISHED erfordert zusätzlich blog:publish.","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"BlogPost (ohne data-Hülle)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlogPost"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:write"]},"delete":{"operationId":"delete_blog_posts_id","tags":["blog"],"summary":"Blogpost löschen","description":"**Scope:** `blog:write`\n\n**Antwort:** { ok: true }","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"{ ok: true }","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:write"]}},"/api/v1/blog/posts/by-slug/{slug}":{"get":{"operationId":"get_blog_posts_by_slug_slug","tags":["blog"],"summary":"Blogpost per Slug lesen","description":"**Scope:** `blog:read`\n\n**Antwort:** BlogPost (ohne data-Hülle)","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"BlogPost (ohne data-Hülle)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlogPost"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:read"]}},"/api/v1/blog/posts/{id}/publish":{"post":{"operationId":"post_blog_posts_id_publish","tags":["blog"],"summary":"Blogpost veröffentlichen","description":"**Scope:** `blog:publish`\n\n**Antwort:** 200 BlogPost (ohne data-Hülle)","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"200 BlogPost (ohne data-Hülle)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlogPost"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:publish"]},"delete":{"operationId":"delete_blog_posts_id_publish","tags":["blog"],"summary":"Blogpost zurückziehen (unpublish)","description":"**Scope:** `blog:publish`\n\n**Antwort:** BlogPost (ohne data-Hülle)","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"BlogPost (ohne data-Hülle)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlogPost"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:publish"]}},"/api/v1/blog/posts/{id}/schedule":{"post":{"operationId":"post_blog_posts_id_schedule","tags":["blog"],"summary":"Blogpost für späteren Zeitpunkt planen","description":"**Scope:** `blog:publish`\n\n**Antwort:** 200 BlogPost (ohne data-Hülle)","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"200 BlogPost (ohne data-Hülle)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlogPost"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:publish"]}},"/api/v1/blog/categories":{"get":{"operationId":"get_blog_categories","tags":["blog"],"summary":"Blog-Kategorien auflisten","description":"**Scope:** `blog:read`\n\n**Antwort:** { categories: BlogCategory[] }","responses":{"200":{"description":"{ categories: BlogCategory[] }","content":{"application/json":{"schema":{"type":"object","required":["categories"],"properties":{"categories":{"type":"array","items":{"$ref":"#/components/schemas/BlogCategory"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:read"]},"post":{"operationId":"post_blog_categories","tags":["blog"],"summary":"Blog-Kategorie anlegen","description":"**Scope:** `blog:write`\n\n**Antwort:** 201 { id, slug, name }","responses":{"201":{"description":"201 { id, slug, name }","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:write"]}},"/api/v1/blog/categories/{id}":{"get":{"operationId":"get_blog_categories_id","tags":["blog"],"summary":"Blog-Kategorie lesen","description":"**Scope:** `blog:read`\n\n**Antwort:** BlogCategory (Datensatz ohne data-Hülle)","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"BlogCategory (Datensatz ohne data-Hülle)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlogCategory"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:read"]},"patch":{"operationId":"patch_blog_categories_id","tags":["blog"],"summary":"Blog-Kategorie ändern","description":"**Scope:** `blog:write`\n\n**Antwort:** BlogCategory (Datensatz ohne data-Hülle)","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"BlogCategory (Datensatz ohne data-Hülle)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlogCategory"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:write"]},"delete":{"operationId":"delete_blog_categories_id","tags":["blog"],"summary":"Blog-Kategorie löschen","description":"**Scope:** `blog:write`\n\n**Antwort:** { ok: true }","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"{ ok: true }","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:write"]}},"/api/v1/blog/tags":{"get":{"operationId":"get_blog_tags","tags":["blog"],"summary":"Blog-Tags auflisten","description":"**Scope:** `blog:read`\n\n**Antwort:** { tags: BlogTag[] }","responses":{"200":{"description":"{ tags: BlogTag[] }","content":{"application/json":{"schema":{"type":"object","required":["tags"],"properties":{"tags":{"type":"array","items":{"$ref":"#/components/schemas/BlogTag"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:read"]},"post":{"operationId":"post_blog_tags","tags":["blog"],"summary":"Blog-Tag anlegen","description":"**Scope:** `blog:write`\n\n**Antwort:** 201 { id, slug, name }","responses":{"201":{"description":"201 { id, slug, name }","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:write"]}},"/api/v1/blog/tags/{id}":{"get":{"operationId":"get_blog_tags_id","tags":["blog"],"summary":"Blog-Tag lesen","description":"**Scope:** `blog:read`\n\n**Antwort:** BlogTag (Datensatz ohne data-Hülle)","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"BlogTag (Datensatz ohne data-Hülle)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlogTag"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:read"]},"patch":{"operationId":"patch_blog_tags_id","tags":["blog"],"summary":"Blog-Tag ändern","description":"**Scope:** `blog:write`\n\n**Antwort:** BlogTag (Datensatz ohne data-Hülle)","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"BlogTag (Datensatz ohne data-Hülle)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlogTag"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:write"]},"delete":{"operationId":"delete_blog_tags_id","tags":["blog"],"summary":"Blog-Tag löschen","description":"**Scope:** `blog:write`\n\n**Antwort:** { ok: true }","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"{ ok: true }","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:write"]}},"/api/v1/blog/media":{"get":{"operationId":"get_blog_media","tags":["blog"],"summary":"Medienbibliothek auflisten","description":"**Scope:** `blog:read`\n\n**Antwort:** { media: BlogMedia[] }","responses":{"200":{"description":"{ media: BlogMedia[] }","content":{"application/json":{"schema":{"type":"object","required":["media"],"properties":{"media":{"type":"array","items":{"$ref":"#/components/schemas/BlogMedia"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:read"]},"post":{"operationId":"post_blog_media","tags":["blog"],"summary":"Medium hochladen","description":"**Scope:** `blog:write`\n\n**Antwort:** 201 BlogMedia ohne width/height (ohne data-Hülle)","responses":{"201":{"description":"201 BlogMedia ohne width/height (ohne data-Hülle)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlogMedia"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:write"]}},"/api/v1/blog/media/{id}":{"get":{"operationId":"get_blog_media_id","tags":["blog"],"summary":"Medium lesen","description":"**Scope:** `blog:read`\n\n**Antwort:** BlogMedia (ohne data-Hülle)","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"BlogMedia (ohne data-Hülle)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlogMedia"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:read"]}},"/api/v1/blog/ai/full-post":{"post":{"operationId":"post_blog_ai_full_post","tags":["blog"],"summary":"KI-Blogpost-Generierung anstoßen","description":"**Scope:** `blog:write`\n\n**Antwort:** 200 { draft, ai: { model, promptHash } } — synchrone Generierung; für lange Läufe die Job-Endpunkte verwenden","responses":{"200":{"description":"200 { draft, ai: { model, promptHash } } — synchrone Generierung; für lange Läufe die Job-Endpunkte verwenden","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:write"]}},"/api/v1/blog/ai/full-post/jobs":{"post":{"operationId":"post_blog_ai_full_post_jobs","tags":["blog"],"summary":"KI-Generierungsjob anlegen","description":"**Scope:** `blog:write`\n\n**Antwort:** 202 { job: Job }","responses":{"202":{"description":"202 { job: Job }","content":{"application/json":{"schema":{"type":"object","required":["job"],"properties":{"job":{"$ref":"#/components/schemas/Job"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:write"]}},"/api/v1/blog/ai/full-post/jobs/{id}":{"get":{"operationId":"get_blog_ai_full_post_jobs_id","tags":["blog"],"summary":"Status eines KI-Generierungsjobs lesen","description":"**Scope:** `blog:read`\n\n**Antwort:** { job: Job }","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"{ job: Job }","content":{"application/json":{"schema":{"type":"object","required":["job"],"properties":{"job":{"$ref":"#/components/schemas/Job"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:read"]}},"/api/v1/blog/ai/full-post/jobs/{id}/resume":{"post":{"operationId":"post_blog_ai_full_post_jobs_id_resume","tags":["blog"],"summary":"Unterbrochenen KI-Generierungsjob fortsetzen","description":"**Scope:** `blog:write`\n\n**Antwort:** 202 { job: Job } (200, wenn der Job bereits abgeschlossen ist)","parameters":[{"name":"id","in":"path","required":true,"description":"ID des Datensatzes (cuid).","schema":{"type":"string"}}],"responses":{"202":{"description":"202 { job: Job } (200, wenn der Job bereits abgeschlossen ist)","content":{"application/json":{"schema":{"type":"object","required":["job"],"properties":{"job":{"$ref":"#/components/schemas/Job"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:write"]}},"/api/v1/openapi.json":{"get":{"operationId":"get_openapi_json","tags":["meta"],"summary":"Maschinenlesbares OpenAPI-3.0-Dokument der gesamten v1-API laden","description":"**Öffentlich** – keine Authentifizierung erforderlich.\n\n**Antwort:** 200 application/json — OpenAPI-3.0.3-Dokument, generiert aus diesem Katalog (lib/api/openapi.ts).\n\n- Öffentlich erreichbar, keine Authentifizierung nötig. Cache-Control: public, max-age=300.","responses":{"200":{"description":"200 application/json — OpenAPI-3.0.3-Dokument, generiert aus diesem Katalog (lib/api/openapi.ts).","content":{"application/json":{"schema":{"type":"object"}}}}},"security":[],"x-scopes":[]}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API-Schlüssel im Authorization-Header: `Authorization: Bearer sk_live_...`. Erstellbar unter Einstellungen → API-Schlüssel."}},"schemas":{"Error":{"type":"object","description":"Fehlerantwort aller v1-Endpunkte.","required":["error"],"properties":{"error":{"type":"string","description":"Lesbare Fehlermeldung (Deutsch)."},"code":{"type":"string","description":"Maschinenlesbarer Code, z. B. plan_no_access oder quota_exceeded."},"allowedValues":{"type":"array","items":{"type":"string"},"description":"Zulässige Werte bei ungültigem Enum-Wert."},"requiredScopes":{"type":"array","items":{"type":"string"},"description":"Fehlende Scopes bei 403."}}},"Pagination":{"type":"object","description":"Seiteninformation von Listen-Endpunkten (bei einigen Ressourcen unter dem Schlüssel meta).","properties":{"total":{"type":"integer","description":"Gesamtzahl der Treffer."},"limit":{"type":"integer","description":"Seitengröße."},"offset":{"type":"integer","description":"Versatz."},"hasMore":{"type":"boolean","description":"Weitere Seiten vorhanden (bei älteren Ressourcen zusätzlich has_more)."}}},"Success":{"type":"object","description":"Antwort von Aktionen ohne Rückgabeobjekt.","properties":{"success":{"type":"boolean","enum":[true]}}},"Address":{"type":"object","description":"Firmenanschrift (strukturiert und als Freitext).","properties":{"street":{"type":"string","nullable":true},"zipCode":{"type":"string","nullable":true},"city":{"type":"string","nullable":true},"country":{"type":"string","nullable":true},"countryCode":{"type":"string","nullable":true,"description":"ISO-3166-1-alpha-2-Code."},"formatted":{"type":"string","nullable":true,"description":"Gepflegter Freitext (Company.address)."}}},"CustomerRef":{"type":"object","description":"Eingebettete Kundenreferenz (Kurzform).","properties":{"id":{"type":"string"},"name":{"type":"string"},"email":{"type":"string","nullable":true},"customerNumber":{"type":"string","nullable":true}}},"InvoiceLine":{"type":"object","description":"Rechnungsposition. `discount` ist ein absoluter Betrag (keine Prozentangabe).","properties":{"id":{"type":"string"},"position":{"type":"integer"},"description":{"type":"string"},"quantity":{"type":"number"},"unitPrice":{"type":"number","description":"Einzelpreis netto."},"taxRate":{"type":"number","description":"Steuersatz in Prozent."},"discount":{"type":"number","description":"Absoluter Rabattbetrag."},"lineType":{"type":"string","enum":["PRODUCT","SERVICE","DISCOUNT_PERCENT","DISCOUNT_ABSOLUTE","SHIPPING","ADVANCE_PAYMENT","CREDIT","TEXT","SUBTOTAL","PAGEBREAK"]},"lineTotal":{"type":"number"}}},"QuoteLine":{"type":"object","description":"Position von Angebot oder Auftragsbestätigung. `discount` ist ein Prozentwert (0–100), anders als bei Rechnungspositionen.","properties":{"id":{"type":"string"},"position":{"type":"integer"},"description":{"type":"string"},"quantity":{"type":"number"},"unit":{"type":"string"},"unitPrice":{"type":"number"},"taxRate":{"type":"number"},"discount":{"type":"number","description":"Prozentwert (0–100)."},"lineType":{"type":"string","enum":["PRODUCT","SERVICE","DISCOUNT_PERCENT","DISCOUNT_ABSOLUTE","SHIPPING","ADVANCE_PAYMENT","CREDIT","TEXT","SUBTOTAL","PAGEBREAK"]},"lineTotal":{"type":"number"}}},"OrderLine":{"type":"object","description":"Auftragsposition mit Liefer- und Abrechnungsfortschritt.","properties":{"id":{"type":"string"},"position":{"type":"integer"},"description":{"type":"string"},"quantity":{"type":"number"},"unit":{"type":"string"},"unitPrice":{"type":"number"},"taxRate":{"type":"number"},"discount":{"type":"number","description":"Prozentwert (0–100)."},"discountMode":{"type":"string","enum":["percent"]},"lineType":{"type":"string","enum":["PRODUCT","SERVICE","DISCOUNT_PERCENT","DISCOUNT_ABSOLUTE","SHIPPING","ADVANCE_PAYMENT","CREDIT","TEXT","SUBTOTAL","PAGEBREAK"]},"lineTotal":{"type":"number"},"deliveredQty":{"type":"number"},"invoicedQty":{"type":"number"}}},"DeliveryNoteLine":{"type":"object","description":"Lieferscheinposition ohne Preisangaben.","properties":{"id":{"type":"string"},"position":{"type":"integer"},"description":{"type":"string"},"quantity":{"type":"number"},"unit":{"type":"string"}}},"CreditNoteLine":{"type":"object","description":"Gutschriftsposition. Menge/Beträge sind am Beleg negativ gespeichert.","properties":{"id":{"type":"string"},"position":{"type":"integer"},"description":{"type":"string"},"quantity":{"type":"number"},"unit":{"type":"string","nullable":true},"unit_price":{"type":"number"},"unitPrice":{"type":"number"},"tax_rate":{"type":"number"},"taxRate":{"type":"number"},"discount":{"type":"number"},"line_type":{"type":"string","nullable":true,"enum":["PRODUCT","SERVICE","DISCOUNT_PERCENT","DISCOUNT_ABSOLUTE","SHIPPING","ADVANCE_PAYMENT","CREDIT","TEXT","SUBTOTAL","PAGEBREAK",null]},"lineType":{"type":"string","nullable":true,"enum":["PRODUCT","SERVICE","DISCOUNT_PERCENT","DISCOUNT_ABSOLUTE","SHIPPING","ADVANCE_PAYMENT","CREDIT","TEXT","SUBTOTAL","PAGEBREAK",null]},"total":{"type":"number"}}},"ScheduleLine":{"type":"object","description":"Position eines Zeitplans für wiederkehrende Rechnungen (JSON-Spalte).","required":["description","quantity","unitPrice","taxRate"],"properties":{"description":{"type":"string"},"quantity":{"type":"number"},"unitPrice":{"type":"number"},"taxRate":{"type":"number"},"discount":{"type":"number","description":"Absoluter Rabattbetrag.","nullable":true},"lineType":{"type":"string","enum":["PRODUCT","SERVICE","DISCOUNT_PERCENT","DISCOUNT_ABSOLUTE","SHIPPING","ADVANCE_PAYMENT","CREDIT","TEXT","SUBTOTAL","PAGEBREAK"],"nullable":true},"articleNumber":{"type":"string","nullable":true}}},"CompanyUsageEntry":{"type":"object","description":"Plan-Verbrauch für eine einzelne Kategorie.","properties":{"current":{"type":"integer"},"limit":{"type":"integer"},"remaining":{"type":"integer"}}},"Invoice":{"type":"object","description":"Rechnung, Proforma oder Gutschrift (invoiceType unterscheidet). Vollständigste Form = GET /api/v1/invoices/{id}.","properties":{"id":{"type":"string"},"invoiceNumber":{"type":"string","description":"Platzhalter ENTWURF-… bis zur Ausstellung."},"invoiceType":{"type":"string","enum":["INVOICE","CREDIT_NOTE","PROFORMA"]},"status":{"type":"string","enum":["DRAFT","FINAL","SENT","PAID","OVERDUE","CANCELLED"]},"documentCategoryId":{"type":"string","nullable":true},"issueDate":{"type":"string","format":"date-time"},"dueDate":{"type":"string","format":"date-time"},"servicePeriodStart":{"type":"string","format":"date-time","nullable":true},"servicePeriodEnd":{"type":"string","format":"date-time","nullable":true},"currency":{"type":"string","example":"EUR"},"netTotal":{"type":"number"},"taxTotal":{"type":"number"},"grossTotal":{"type":"number"},"reverseCharge":{"type":"boolean"},"taxExemptionReason":{"type":"string","enum":["NONE","SMALL_BUSINESS","REVERSE_CHARGE_EU","REVERSE_CHARGE_EU_NO_VAT","THIRD_COUNTRY","INTRA_COMMUNITY","EXPORT_DELIVERY","REVERSE_CHARGE_13B","INTERNAL_SUPPLY","OTHER_EXEMPTION"],"nullable":true},"taxExemptionNote":{"type":"string","nullable":true},"taxCountry":{"type":"string","nullable":true},"deliveryCountry":{"type":"string","nullable":true},"ossApplicable":{"type":"boolean"},"ossCountry":{"type":"string","nullable":true},"orderNumber":{"type":"string","nullable":true},"order_number":{"type":"string","nullable":true,"description":"snake_case-Alias von orderNumber."},"buyerReference":{"type":"string","nullable":true},"buyer_reference":{"type":"string","nullable":true,"description":"snake_case-Alias von buyerReference."},"e_invoice":{"type":"object","nullable":true,"description":"Factur-X/ZUGFeRD-Audit-Metadaten (aus Invoice.metadata abgeleitet).","properties":{"publicSectorRecipient":{"type":"string"},"publicSectorDepartment":{"type":"string"},"format":{"type":"string"},"submissionChannel":{"type":"string"},"submissionPortalUrl":{"type":"string"},"submissionReference":{"type":"string"},"submissionStatus":{"type":"string"},"submissionConfirmedAt":{"type":"string"},"submissionFeedback":{"type":"string"}}},"headerText":{"type":"string","nullable":true},"notes":{"type":"string","nullable":true},"footer":{"type":"string","nullable":true,"description":"Nur in der Detailantwort (GET /invoices/{id})."},"customTitle":{"type":"string","nullable":true},"pdfWithoutLetterhead":{"type":"boolean"},"invoiceDateIsServiceDate":{"type":"boolean"},"useShippingAddress":{"type":"boolean"},"shippingName":{"type":"string","nullable":true},"shippingStreet":{"type":"string","nullable":true},"shippingZipCode":{"type":"string","nullable":true},"shippingCity":{"type":"string","nullable":true},"shippingCountry":{"type":"string","nullable":true},"sentAt":{"type":"string","format":"date-time","nullable":true,"description":"Nur in der Detailantwort."},"paidAt":{"type":"string","format":"date-time","nullable":true,"description":"Nur in der Detailantwort."},"lockedAt":{"type":"string","format":"date-time","nullable":true,"description":"Nur in der Detailantwort."},"customer":{"description":"In der Listenantwort nur id/name/email; die Detailantwort ergänzt address und taxId.","type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"email":{"type":"string","nullable":true},"address":{"type":"string","nullable":true,"description":"Nur in der Detailantwort."},"taxId":{"type":"string","nullable":true,"description":"Nur in der Detailantwort."}}},"lines":{"type":"array","items":{"$ref":"#/components/schemas/InvoiceLine"}},"payments":{"type":"array","description":"Nur in der Detailantwort. Eigene, schlankere Form als das Payment-Schema von GET .../payments (date statt payment_date, method statt payment_method, ohne notes/createdAt).","items":{"type":"object","properties":{"id":{"type":"string"},"amount":{"type":"number"},"date":{"type":"string","format":"date-time"},"method":{"type":"string","enum":["BANK_TRANSFER","CASH","CREDIT_CARD","PAYPAL","OTHER"]},"reference":{"type":"string","nullable":true}}}},"paidAmount":{"type":"number","description":"Nur in der Detailantwort: Summe der erfassten Zahlungen."},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}},"example":{"id":"clx1invoice0001","invoiceNumber":"RE-2026-0042","invoiceType":"INVOICE","status":"SENT","currency":"EUR","netTotal":100,"taxTotal":20,"grossTotal":120,"customer":{"id":"clx1customer001","name":"Muster GmbH","email":"buchhaltung@muster.example"}}},"CreditNoteListItem":{"type":"object","description":"Gutschrift in der Listenantwort — ohne Positionen (lines).","properties":{"id":{"type":"string"},"invoice_number":{"type":"string"},"invoiceNumber":{"type":"string"},"numberIssued":{"type":"boolean","description":"false solange die Platzhalternummer ENTWURF-… gilt."},"credit_note_reason":{"type":"string","enum":["CANCELLATION","RETURN","PRICE_CORRECTION","PAYOUT","COMMISSION","GOODWILL","OTHER"]},"creditNoteReason":{"type":"string","enum":["CANCELLATION","RETURN","PRICE_CORRECTION","PAYOUT","COMMISSION","GOODWILL","OTHER"]},"status":{"type":"string","enum":["DRAFT","FINAL","SENT","PAID","OVERDUE","CANCELLED"]},"issue_date":{"type":"string","format":"date-time"},"recipient":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"email":{"type":"string","nullable":true}}},"currency":{"type":"string"},"net_total":{"type":"number"},"tax_total":{"type":"number"},"gross_total":{"type":"number"},"notes":{"type":"string","nullable":true},"footer":{"type":"string","nullable":true},"metadata":{"type":"object","nullable":true},"payout":{"type":"object","nullable":true,"properties":{"id":{"type":"string"},"status":{"type":"string","enum":["PENDING","APPROVED","EXECUTED","FAILED","CANCELLED"]},"amount":{"type":"number"},"executed_at":{"type":"string","format":"date-time","nullable":true},"transaction_id":{"type":"string","nullable":true}}},"parent_invoice_id":{"type":"string","nullable":true},"created_at":{"type":"string","format":"date-time"}},"example":{"id":"clx1creditnote01","invoice_number":"GS-2026-0007","invoiceNumber":"GS-2026-0007","numberIssued":true,"credit_note_reason":"PAYOUT","creditNoteReason":"PAYOUT","status":"PAID","currency":"EUR","gross_total":-50}},"CreditNote":{"type":"object","description":"Gutschriftsdetails (GET /api/v1/credit-notes/{id}).","properties":{"id":{"type":"string"},"invoice_number":{"type":"string"},"invoiceNumber":{"type":"string"},"numberIssued":{"type":"boolean"},"credit_note_reason":{"type":"string","enum":["CANCELLATION","RETURN","PRICE_CORRECTION","PAYOUT","COMMISSION","GOODWILL","OTHER"]},"creditNoteReason":{"type":"string","enum":["CANCELLATION","RETURN","PRICE_CORRECTION","PAYOUT","COMMISSION","GOODWILL","OTHER"]},"status":{"type":"string","enum":["DRAFT","FINAL","SENT","PAID","OVERDUE","CANCELLED"]},"issue_date":{"type":"string","format":"date-time"},"due_date":{"type":"string","format":"date-time"},"recipient":{"type":"object","description":"Kunde inkl. Bankdaten (für die Auszahlung).","properties":{"id":{"type":"string"},"name":{"type":"string"},"email":{"type":"string","nullable":true},"address":{"type":"string","nullable":true},"iban":{"type":"string","nullable":true},"bic":{"type":"string","nullable":true},"bank_name":{"type":"string","nullable":true},"bank_account_holder":{"type":"string","nullable":true}}},"currency":{"type":"string"},"net_total":{"type":"number"},"tax_total":{"type":"number"},"gross_total":{"type":"number"},"reverse_charge":{"type":"boolean"},"tax_exemption_reason":{"type":"string","enum":["NONE","SMALL_BUSINESS","REVERSE_CHARGE_EU","REVERSE_CHARGE_EU_NO_VAT","THIRD_COUNTRY","INTRA_COMMUNITY","EXPORT_DELIVERY","REVERSE_CHARGE_13B","INTERNAL_SUPPLY","OTHER_EXEMPTION"],"nullable":true},"lines":{"type":"array","items":{"$ref":"#/components/schemas/CreditNoteLine"}},"notes":{"type":"string","nullable":true},"footer":{"type":"string","nullable":true},"metadata":{"type":"object","nullable":true},"parent_invoice":{"type":"object","nullable":true,"properties":{"id":{"type":"string"},"invoice_number":{"type":"string"},"gross_total":{"type":"number"}}},"payout":{"type":"object","nullable":true,"properties":{"id":{"type":"string"},"status":{"type":"string","enum":["PENDING","APPROVED","EXECUTED","FAILED","CANCELLED"]},"amount":{"type":"number"},"currency":{"type":"string"},"reference":{"type":"string","nullable":true},"iban":{"type":"string","nullable":true},"bic":{"type":"string","nullable":true},"bank_name":{"type":"string","nullable":true},"bank_account_holder":{"type":"string","nullable":true},"approved_at":{"type":"string","format":"date-time","nullable":true},"approved_by":{"type":"string","nullable":true},"executed_at":{"type":"string","format":"date-time","nullable":true},"transaction_id":{"type":"string","nullable":true},"failure_reason":{"type":"string","nullable":true},"metadata":{"type":"object","nullable":true},"created_at":{"type":"string","format":"date-time"}}},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"Customer":{"type":"object","description":"Kunde. GET /customers/{id} ergänzt zusätzlich invoiceCount und recentInvoices (hier nicht Teil des geteilten Schemas).","properties":{"id":{"type":"string"},"customerNumber":{"type":"string","nullable":true},"name":{"type":"string"},"company":{"type":"string","description":"Kompatibilitäts-Alias von name (ChatCMM)."},"salutation":{"type":"string","nullable":true},"title":{"type":"string","nullable":true},"firstName":{"type":"string","nullable":true},"lastName":{"type":"string","nullable":true},"department":{"type":"string","nullable":true},"email":{"type":"string","nullable":true},"phone":{"type":"string","nullable":true},"mobile":{"type":"string","nullable":true},"website":{"type":"string","nullable":true},"taxId":{"type":"string","nullable":true},"tax_id":{"type":"string","nullable":true,"description":"Kompatibilitäts-Alias von taxId."},"taxIdValidated":{"type":"boolean"},"taxIdValidatedAt":{"type":"string","format":"date-time","nullable":true},"taxNumber":{"type":"string","nullable":true},"registryCourt":{"type":"string","nullable":true},"registryNumber":{"type":"string","nullable":true},"bmdAccountNumber":{"type":"string","nullable":true},"bmdRevenueAccount":{"type":"string","nullable":true},"datevId":{"type":"string","nullable":true},"street":{"type":"string","nullable":true},"zipCode":{"type":"string","nullable":true},"city":{"type":"string","nullable":true},"state":{"type":"string","nullable":true},"country":{"type":"string","nullable":true},"countryCode":{"type":"string","nullable":true},"shippingName":{"type":"string","nullable":true},"shippingCompany":{"type":"string","nullable":true},"shippingStreet":{"type":"string","nullable":true},"shippingZipCode":{"type":"string","nullable":true},"shippingCity":{"type":"string","nullable":true},"shippingState":{"type":"string","nullable":true},"shippingCountry":{"type":"string","nullable":true},"shippingCountryCode":{"type":"string","nullable":true},"address":{"type":"string","nullable":true,"description":"Gepflegter Freitext, sonst aus den strukturierten Feldern zusammengesetzt."},"shippingAddress":{"type":"string","nullable":true},"bankAccountHolder":{"type":"string","nullable":true},"bankName":{"type":"string","nullable":true},"iban":{"type":"string","nullable":true},"bic":{"type":"string","nullable":true},"sepaMandateReference":{"type":"string","nullable":true},"sepaMandateDate":{"type":"string","format":"date-time","nullable":true},"sepaMandateType":{"type":"string","enum":["CORE","B2B"],"nullable":true},"sepaMandateStatus":{"type":"string","enum":["ACTIVE","REVOKED","EXPIRED"],"nullable":true},"sepaSequenceType":{"type":"string","enum":["FRST","RCUR","OOFF","FNAL"],"nullable":true},"paymentTermDays":{"type":"integer","nullable":true},"discountPercent":{"type":"number","nullable":true},"discountDays":{"type":"integer","nullable":true},"defaultTaxExemptionReason":{"type":"string","enum":["NONE","SMALL_BUSINESS","REVERSE_CHARGE_EU","REVERSE_CHARGE_EU_NO_VAT","THIRD_COUNTRY","INTRA_COMMUNITY","EXPORT_DELIVERY","REVERSE_CHARGE_13B","INTERNAL_SUPPLY","OTHER_EXEMPTION"],"nullable":true},"defaultTaxExemptionNote":{"type":"string","nullable":true},"contacts":{"nullable":true,"description":"Freies JSON (Array oder Objekt)."},"billingPrefs":{"nullable":true,"description":"Freies JSON."},"metadata":{"nullable":true,"description":"Freies JSON."},"notes":{"type":"string","nullable":true},"category":{"type":"string","nullable":true},"customerSince":{"type":"string","format":"date-time","nullable":true},"isArchived":{"type":"boolean"},"archivedAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"},"created_at":{"type":"string","format":"date-time","description":"Kompatibilitäts-Alias von createdAt."},"updatedAt":{"type":"string","format":"date-time"}},"example":{"id":"clx1customer001","customerNumber":"K-1042","name":"Muster GmbH","email":"buchhaltung@muster.example","country":"Österreich","isArchived":false}},"Product":{"type":"object","description":"Produkt- bzw. Leistungsstammdatensatz.","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"sku":{"type":"string","nullable":true},"isService":{"type":"boolean"},"price":{"type":"number"},"taxRate":{"type":"number","description":"Steuersatz in Prozent."},"unit":{"type":"string","example":"Stück"},"isActive":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}},"example":{"id":"clx1product0001","name":"Beratung (Stunde)","isService":true,"price":120,"taxRate":20,"unit":"Stunde","isActive":true}},"Quote":{"type":"object","description":"Angebot.","properties":{"id":{"type":"string"},"quoteNumber":{"type":"string"},"status":{"type":"string","enum":["DRAFT","SENT","ACCEPTED","REJECTED","EXPIRED","CONVERTED"],"description":"Gespeicherter Status (ohne EXPIRED)."},"displayStatus":{"type":"string","enum":["DRAFT","SENT","ACCEPTED","REJECTED","EXPIRED","CONVERTED","EXPIRED"],"description":"Angezeigter Status inkl. abgeleitetem EXPIRED (nie gespeichert)."},"documentCategoryId":{"type":"string","nullable":true},"issueDate":{"type":"string","format":"date-time"},"validUntil":{"type":"string","format":"date-time"},"servicePeriodStart":{"type":"string","format":"date-time","nullable":true},"servicePeriodEnd":{"type":"string","format":"date-time","nullable":true},"currency":{"type":"string"},"netTotal":{"type":"number"},"taxTotal":{"type":"number"},"grossTotal":{"type":"number"},"reverseCharge":{"type":"boolean"},"headerText":{"type":"string","nullable":true},"notes":{"type":"string","nullable":true},"footer":{"type":"string","nullable":true},"convertedToInvoiceId":{"type":"string","nullable":true},"lockedAt":{"type":"string","format":"date-time","nullable":true},"currentVersion":{"type":"integer"},"customer":{"$ref":"#/components/schemas/CustomerRef"},"lines":{"type":"array","items":{"$ref":"#/components/schemas/QuoteLine"}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"OrderConfirmation":{"type":"object","description":"Auftragsbestätigung (AB).","properties":{"id":{"type":"string"},"confirmationNumber":{"type":"string"},"status":{"type":"string","enum":["DRAFT","SENT","CONFIRMED","IN_PROGRESS","COMPLETED","CANCELLED"]},"documentCategoryId":{"type":"string","nullable":true},"issueDate":{"type":"string","format":"date-time"},"deliveryDate":{"type":"string","format":"date-time","nullable":true},"validUntil":{"type":"string","format":"date-time","nullable":true},"currency":{"type":"string"},"netTotal":{"type":"number"},"taxTotal":{"type":"number"},"grossTotal":{"type":"number"},"reverseCharge":{"type":"boolean"},"notes":{"type":"string","nullable":true},"footer":{"type":"string","nullable":true},"deliveryTerms":{"type":"string","nullable":true},"paymentTerms":{"type":"string","nullable":true},"quoteId":{"type":"string","nullable":true},"convertedToInvoiceId":{"type":"string","nullable":true},"lockedAt":{"type":"string","format":"date-time","nullable":true},"currentVersion":{"type":"integer"},"customer":{"$ref":"#/components/schemas/CustomerRef"},"lines":{"type":"array","items":{"$ref":"#/components/schemas/QuoteLine"}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"DeliveryNote":{"type":"object","description":"Lieferschein ohne Preisangaben.","properties":{"id":{"type":"string"},"deliveryNumber":{"type":"string"},"status":{"type":"string","enum":["DRAFT","SENT","DELIVERED","CONVERTED"]},"documentCategoryId":{"type":"string","nullable":true},"issueDate":{"type":"string","format":"date-time"},"deliveryDate":{"type":"string","format":"date-time","nullable":true},"shippingAddress":{"type":"string","nullable":true},"notes":{"type":"string","nullable":true},"orderId":{"type":"string","nullable":true},"convertedToInvoiceId":{"type":"string","nullable":true},"lockedAt":{"type":"string","format":"date-time","nullable":true},"currentVersion":{"type":"integer"},"customer":{"$ref":"#/components/schemas/CustomerRef"},"lines":{"type":"array","items":{"$ref":"#/components/schemas/DeliveryNoteLine"}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Order":{"type":"object","description":"Auftrag. GET /orders/{id} ergänzt zusätzlich stats, invoices und deliveryNotes (hier nicht Teil des geteilten Schemas).","properties":{"id":{"type":"string"},"orderNumber":{"type":"string"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"status":{"type":"string","enum":["DRAFT","ACTIVE","ON_HOLD","COMPLETED","CANCELLED"]},"documentCategoryId":{"type":"string","nullable":true},"startDate":{"type":"string","format":"date-time","nullable":true},"endDate":{"type":"string","format":"date-time","nullable":true},"currency":{"type":"string"},"totalBudget":{"type":"number","description":"Aus den Positionen berechnete Nettosumme."},"notes":{"type":"string","nullable":true},"quoteId":{"type":"string","nullable":true},"orderConfirmationId":{"type":"string","nullable":true},"lockedAt":{"type":"string","format":"date-time","nullable":true},"currentVersion":{"type":"integer"},"customer":{"$ref":"#/components/schemas/CustomerRef"},"lines":{"type":"array","items":{"$ref":"#/components/schemas/OrderLine"}},"totals":{"type":"object","description":"Aus den Positionen berechnete Summen (lib/orders.ts computeOrderTotals).","properties":{"netTotal":{"type":"number"},"taxTotal":{"type":"number"},"grossTotal":{"type":"number"}}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Schedule":{"type":"object","description":"Zeitplan für wiederkehrende Rechnungen. GET /recurring-schedules/{id} ergänzt generatedInvoices.","properties":{"id":{"type":"string"},"customerId":{"type":"string"},"customer":{"type":"object","nullable":true,"properties":{"id":{"type":"string"},"name":{"type":"string"},"email":{"type":"string","nullable":true}}},"documentCategoryId":{"type":"string","nullable":true},"name":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"status":{"type":"string","enum":["ACTIVE","PAUSED","ENDED","ARCHIVED"]},"frequency":{"type":"string","enum":["WEEKLY","BIWEEKLY","MONTHLY","QUARTERLY","SEMIANNUALLY","ANNUALLY"]},"intervalMultiplier":{"type":"integer"},"dayOfMonth":{"type":"integer","nullable":true},"dayOfWeek":{"type":"integer","nullable":true,"description":"0–6."},"useLastDayOfMonth":{"type":"boolean"},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time","nullable":true},"maxOccurrences":{"type":"integer","nullable":true},"createAsDraft":{"type":"boolean"},"autoSendEmail":{"type":"boolean"},"notifyOnCreate":{"type":"boolean"},"markAsPaid":{"type":"boolean"},"emailCc":{"type":"string","nullable":true},"emailBcc":{"type":"string","nullable":true},"paymentTermDays":{"type":"integer","nullable":true},"discountPercent":{"type":"number","nullable":true},"discountDays":{"type":"integer","nullable":true},"orderNumber":{"type":"string","nullable":true},"buyerReference":{"type":"string","nullable":true},"externalId":{"type":"string","nullable":true},"totalDiscountPercent":{"type":"number","nullable":true},"isOSS":{"type":"boolean"},"showServiceDateNotice":{"type":"boolean"},"taxExemptionReason":{"type":"string","enum":["NONE","SMALL_BUSINESS","REVERSE_CHARGE_EU","REVERSE_CHARGE_EU_NO_VAT","THIRD_COUNTRY","INTRA_COMMUNITY","EXPORT_DELIVERY","REVERSE_CHARGE_13B","INTERNAL_SUPPLY","OTHER_EXEMPTION"]},"taxExemptionNote":{"type":"string","nullable":true},"taxCountry":{"type":"string","nullable":true},"headerText":{"type":"string","nullable":true},"notes":{"type":"string","nullable":true},"footer":{"type":"string","nullable":true},"lines":{"type":"array","items":{"$ref":"#/components/schemas/ScheduleLine"}},"lastGeneratedAt":{"type":"string","format":"date-time","nullable":true},"nextGenerationDate":{"type":"string","format":"date-time","nullable":true},"generatedCount":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"generatedInvoices":{"type":"array","description":"Nur in der Detailantwort (GET /recurring-schedules/{id}).","items":{"type":"object","properties":{"id":{"type":"string"},"invoiceNumber":{"type":"string"},"status":{"type":"string"},"issueDate":{"type":"string","format":"date-time"},"grossTotal":{"type":"number"}}}}}},"Document":{"type":"object","description":"Belegmetadaten (Dokumentenablage). Der Dateiinhalt läuft über GET .../download.","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"type":{"type":"string","enum":["INVOICE_PDF","RECEIPT","CONTRACT","OFFER","DELIVERY_NOTE","CORRESPONDENCE","TAX_DOCUMENT","OTHER"]},"mimeType":{"type":"string"},"size":{"type":"integer","description":"Dateigröße in Bytes."},"storageProvider":{"type":"string","enum":["LOCAL","S3"]},"tags":{"type":"array","items":{"type":"string"}},"isArchived":{"type":"boolean"},"archivedAt":{"type":"string","format":"date-time","nullable":true},"invoiceId":{"type":"string","nullable":true},"customerId":{"type":"string","nullable":true},"documentCategoryId":{"type":"string","nullable":true},"createdById":{"type":"string","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Webhook":{"type":"object","description":"Webhook-Endpunkt. secret erscheint nur einmalig in der Antwort von POST /webhooks.","properties":{"id":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"},"secret":{"type":"string","description":"Nur in der Antwort von POST /webhooks enthalten."},"events":{"type":"array","items":{"type":"string","enum":["INVOICE_CREATED","INVOICE_UPDATED","INVOICE_SENT","INVOICE_PAID","INVOICE_OVERDUE","INVOICE_CANCELLED","CREDIT_NOTE_CREATED","CREDIT_NOTE_PAID","PAYOUT_APPROVED","PAYOUT_EXECUTED","PAYOUT_FAILED","PAYOUT_CANCELLED","CUSTOMER_CREATED","CUSTOMER_UPDATED","CUSTOMER_DELETED","REMINDER_SENT","REMINDER_ESCALATED","BLOG_POST_CREATED","BLOG_POST_UPDATED","BLOG_POST_PUBLISHED","BLOG_POST_UNPUBLISHED","BLOG_COMMENT_CREATED","BLOG_COMMENT_APPROVED"]}},"is_active":{"type":"boolean"},"max_retries":{"type":"integer"},"retry_delay_ms":{"type":"integer"},"rate_limit_per_min":{"type":"integer","nullable":true},"headers":{"type":"object","nullable":true,"description":"Nur in der Detailantwort."},"metadata":{"type":"object","nullable":true,"description":"Nur in der Detailantwort."},"total_deliveries":{"type":"integer"},"recent_deliveries":{"type":"array","description":"Letzte 10 Zustellversuche, nur in der Detailantwort.","items":{"type":"object","properties":{"id":{"type":"string"},"event_type":{"type":"string","enum":["INVOICE_CREATED","INVOICE_UPDATED","INVOICE_SENT","INVOICE_PAID","INVOICE_OVERDUE","INVOICE_CANCELLED","CREDIT_NOTE_CREATED","CREDIT_NOTE_PAID","PAYOUT_APPROVED","PAYOUT_EXECUTED","PAYOUT_FAILED","PAYOUT_CANCELLED","CUSTOMER_CREATED","CUSTOMER_UPDATED","CUSTOMER_DELETED","REMINDER_SENT","REMINDER_ESCALATED","BLOG_POST_CREATED","BLOG_POST_UPDATED","BLOG_POST_PUBLISHED","BLOG_POST_UNPUBLISHED","BLOG_COMMENT_CREATED","BLOG_COMMENT_APPROVED"]},"status":{"type":"string","enum":["PENDING","SUCCESS","FAILED","RETRYING"]},"attempts":{"type":"integer"},"response_status":{"type":"integer","nullable":true},"created_at":{"type":"string","format":"date-time"},"completed_at":{"type":"string","format":"date-time","nullable":true}}}},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"Company":{"type":"object","description":"Firmenstammdaten, Nummernkreis-Konfiguration, Plan-Funktionen und Verbrauch (read-only).","properties":{"id":{"type":"string"},"name":{"type":"string"},"address":{"$ref":"#/components/schemas/Address"},"contact":{"type":"object","properties":{"phone":{"type":"string","nullable":true},"email":{"type":"string","nullable":true},"website":{"type":"string","nullable":true}}},"legal":{"type":"object","properties":{"registrationNumber":{"type":"string","nullable":true},"managingDirector":{"type":"string","nullable":true}}},"tax":{"type":"object","properties":{"vatId":{"type":"string","nullable":true},"taxNumber":{"type":"string","nullable":true},"isSmallBusiness":{"type":"boolean"},"ossRegistered":{"type":"boolean"},"ossRegistrationCountry":{"type":"string","nullable":true},"ossRegistrationNumber":{"type":"string","nullable":true}}},"bank":{"type":"object","properties":{"bankName":{"type":"string","nullable":true},"iban":{"type":"string","nullable":true},"bic":{"type":"string","nullable":true},"accountHolder":{"type":"string","nullable":true},"sepaCreditorId":{"type":"string","nullable":true},"paypalMeHandle":{"type":"string","nullable":true}}},"currency":{"type":"string"},"paymentTerms":{"type":"object","properties":{"paymentTermDays":{"type":"integer","nullable":true},"earlyPaymentDiscount":{"type":"number","nullable":true},"earlyPaymentDays":{"type":"integer","nullable":true}}},"dunning":{"type":"object","properties":{"reminderFee1":{"type":"number","nullable":true},"reminderFee2":{"type":"number","nullable":true},"reminderFee3":{"type":"number","nullable":true},"defaultInterestRate":{"type":"number","nullable":true}}},"branding":{"type":"object","properties":{"logoUrl":{"type":"string","nullable":true},"showClaribillBranding":{"type":"boolean"},"footerText":{"type":"string","nullable":true},"invoiceHeaderText":{"type":"string","nullable":true},"invoiceFooterText":{"type":"string","nullable":true}}},"numberRanges":{"type":"object","properties":{"invoiceNumberConfig":{"type":"object","nullable":true},"documentFilenameConfig":{"type":"object","nullable":true}}},"plan":{"type":"object","description":"adminPlanOverride fließt in plan/features/limits ein, wird aber nie ausgegeben.","properties":{"plan":{"type":"string","description":"Effektiver Plan inkl. Trial/Admin-Übersteuerung."},"subscriptionPlan":{"type":"string","enum":["FREE","STARTER","PROFESSIONAL","ENTERPRISE"]},"subscriptionExpiresAt":{"type":"string","format":"date-time","nullable":true},"trialEndsAt":{"type":"string","format":"date-time","nullable":true},"features":{"type":"object","additionalProperties":{"type":"boolean"}},"limits":{"type":"object","additionalProperties":{"type":"number"}}}},"usage":{"type":"object","properties":{"documents":{"$ref":"#/components/schemas/CompanyUsageEntry"},"customers":{"$ref":"#/components/schemas/CompanyUsageEntry"},"emails":{"$ref":"#/components/schemas/CompanyUsageEntry"},"storage":{"$ref":"#/components/schemas/CompanyUsageEntry"},"apiCalls":{"$ref":"#/components/schemas/CompanyUsageEntry"},"users":{"$ref":"#/components/schemas/CompanyUsageEntry"}}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Payment":{"type":"object","description":"Rechnungszahlung. snake_case ist führend, camelCase kommt als Alias dazu (lib/invoice-payments.ts).","properties":{"id":{"type":"string"},"amount":{"type":"number"},"payment_date":{"type":"string","format":"date-time"},"paymentDate":{"type":"string","format":"date-time"},"payment_method":{"type":"string","enum":["bank_transfer","cash","credit_card","paypal","other"],"description":"Kleingeschrieben."},"paymentMethod":{"type":"string","enum":["bank_transfer","cash","credit_card","paypal","other"],"description":"Kleingeschrieben."},"reference":{"type":"string","nullable":true},"notes":{"type":"string","nullable":true},"created_at":{"type":"string","format":"date-time"},"createdAt":{"type":"string","format":"date-time"}}},"BlogPost":{"type":"object","description":"Blogpost. Achtung: Die Routen liefern dieses Objekt nicht unter { data: … } — Liste antwortet { posts: [...], pagination }, Detail liefert das Objekt bare (ohne Hülle).","properties":{"id":{"type":"string"},"slug":{"type":"string"},"title":{"type":"string"},"excerpt":{"type":"string","nullable":true},"content":{"type":"string","description":"Sanitiertes HTML."},"status":{"type":"string","enum":["DRAFT","SCHEDULED","PUBLISHED","ARCHIVED"]},"publishedAt":{"type":"string","format":"date-time","nullable":true},"scheduledFor":{"type":"string","format":"date-time","nullable":true},"author":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string","nullable":true},"email":{"type":"string","nullable":true}}},"featuredImage":{"type":"object","nullable":true,"properties":{"id":{"type":"string"},"url":{"type":"string"},"alt":{"type":"string","nullable":true}}},"ogImage":{"type":"object","nullable":true,"properties":{"id":{"type":"string"},"url":{"type":"string"},"alt":{"type":"string","nullable":true}}},"metaTitle":{"type":"string","nullable":true},"metaDescription":{"type":"string","nullable":true},"canonical":{"type":"string","nullable":true},"aiGenerated":{"type":"boolean"},"aiModel":{"type":"string","nullable":true},"aiPromptHash":{"type":"string","nullable":true},"categories":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"}}}},"tags":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"}}}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"url":{"type":"string","description":"Relativer Pfad /blog/{slug}."}}},"BlogCategory":{"type":"object","description":"Blog-Kategorie. Die Antwort von POST enthält nur id/slug/name (Teilmenge dieses Schemas).","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"description":{"type":"string","nullable":true,"description":"Nur in der Listenantwort."},"parentId":{"type":"string","nullable":true,"description":"Nur in der Listenantwort."},"postCount":{"type":"integer","description":"Nur in der Listenantwort."}}},"BlogTag":{"type":"object","description":"Blog-Tag. Die Antwort von POST enthält nur id/slug/name (Teilmenge dieses Schemas).","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"postCount":{"type":"integer","description":"Nur in der Listenantwort."}}},"BlogMedia":{"type":"object","description":"Bild der Medienbibliothek. width/height sind nur in der Listenantwort enthalten.","properties":{"id":{"type":"string"},"url":{"type":"string"},"alt":{"type":"string","nullable":true},"mimeType":{"type":"string"},"bytes":{"type":"integer"},"width":{"type":"integer","nullable":true,"description":"Nur in der Listenantwort."},"height":{"type":"integer","nullable":true,"description":"Nur in der Listenantwort."},"createdAt":{"type":"string","format":"date-time"}}},"Job":{"type":"object","description":"Asynchroner KI-Blogpost-Generierungsjob. Geliefert unter { job: … }, nicht unter { data: … }.","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["QUEUED","RUNNING","SUCCEEDED","FAILED"]},"statusUrl":{"type":"string","description":"Relativer Pfad zu GET .../jobs/{id}."},"result":{"nullable":true,"description":"Generierter Blogpost-Entwurf (freies JSON), nur bei status SUCCEEDED."},"ai":{"type":"object","nullable":true,"description":"Nur bei status SUCCEEDED.","properties":{"model":{"type":"string","nullable":true},"promptHash":{"type":"string","nullable":true}}},"error":{"type":"string","nullable":true,"description":"Nur bei status FAILED."},"attempts":{"type":"integer"},"lockedAt":{"type":"string","format":"date-time","nullable":true},"completedAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Ungültige Eingabe (Pflichtfeld, Enum-Wert, Zahlen-/Datumsformat, Geschäftsregel).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Ungültiger Status","allowedValues":["DRAFT","FINAL","SENT"]}}}},"Unauthorized":{"description":"Authorization-Header fehlt oder der API-Schlüssel ist ungültig bzw. deaktiviert.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Unauthorized"}}}},"Forbidden":{"description":"Scope fehlt, der Plan hat keinen API-Zugang (code \"plan_no_access\") oder ein Plan-Limit ist erschöpft.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Fehlende Berechtigung","requiredScopes":["invoices:write"]}}}},"NotFound":{"description":"Datensatz existiert nicht oder gehört zu einer anderen Firma.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Nicht gefunden"}}}},"Conflict":{"description":"Zustandskonflikt: falscher Status für die Aktion, gleichzeitige Änderung oder bereits vergebene Nummer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Die Rechnung ist bereits versendet"}}}},"TooManyRequests":{"description":"Monatskontingent erschöpft (code \"quota_exceeded\") oder Rate-Limit erreicht.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Monatliches API-Kontingent erschöpft","code":"quota_exceeded"}}}}}}}