{
  "openapi": "3.1.0",
  "info": {
    "title": "epago Public API",
    "version": "1.2.0",
    "description": "Versionierte Public API von epago (GoBD-konforme Doppik fuer KMU). Ein duenner, validierter Mantel um die bestehende Service-Schicht: externe Schreibzugriffe durchlaufen EXAKT dieselbe Validierung wie die Oberflaeche (Soll=Haben, Steuerautomatik, Periodensperre, Storno statt Loeschen). Authentifizierung per API-Schluessel mit Scopes (read/write) und Rate-Limit.\n\n## Authentifizierung\n\nDer Schluessel geht als `Authorization: Bearer epago_…` oder im Header `x-api-key`. Er gehoert zu genau einem Mandanten, und der Mandant kommt immer aus dem Schluessel, nie aus dem Anfragekoerper. Zwei Scopes: `read` liest, `write` schreibt und liest mit. Jede Operation nennt ihren Scope in `x-scope`.\n\n## Grenzen\n\nJe Schluessel gelten 60 Anfragen pro Minute in einem Schiebefenster (am Schluessel einstellbar), davor zusaetzlich 600 Anfragen pro Minute je IP. Das Limit gilt ueber alle Instanzen hinweg. Jede Antwort traegt den Zustand des Fensters in den Headern X-RateLimit-Limit, X-RateLimit-Remaining und X-RateLimit-Reset (Unix-Zeit in Sekunden); eine Absage mit 429 zusaetzlich Retry-After (Sekunden bis zum Fensterende). Wird das Limit ueberschritten, antwortet die API mit 429 und dem Code `rate_limit_exceeded`.\n\n## Fehlerformat\n\nJeder Fehler hat denselben Koerper und traegt keinen Stacktrace:\n\n```json\n{ \"error\": { \"code\": \"insufficient_scope\", \"message\": \"…\" } }\n```\n\n`code` ist maschinenlesbar, `message` ist deutscher Text fuer Menschen. Einzelne Absagen tragen NEBEN `code` und `message` weitere Felder, wenn die Antwort ohne sie eine Sackgasse waere: eine Zahlung, die einen Rest offen laesst, liefert Vorschlag, Restbetrag und Optionen mit. Solche Felder sind additiv, ein Client, der nur `code` und `message` liest, bleibt gueltig.\n\n## Versionierung\n\nDie Version steht im Pfad (`/api/v1`). Innerhalb von v1 wird nur additiv geaendert: neue Endpunkte, neue optionale Felder, neue Fehlerfelder. Ein bestehendes Feld verschwindet nicht und wechselt nicht die Bedeutung. `info.version` folgt SemVer, eine neue Minor-Nummer ist eine additive Erweiterung. Eine nicht additive Aenderung bekaeme `/api/v2`. Der Aenderungsverlauf steht auf https://epago.de/entwickler.",
    "contact": {
      "name": "epago",
      "url": "https://epago.de"
    },
    "license": {
      "name": "Proprietary"
    }
  },
  "servers": [
    {
      "url": "https://app.epago.de/api/v1",
      "description": "Production"
    },
    {
      "url": "http://localhost:3000/api/v1",
      "description": "Local dev"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    },
    {
      "apiKeyHeader": []
    }
  ],
  "tags": [
    {
      "name": "Allgemein",
      "description": "Verbindungstest & Key-Info"
    },
    {
      "name": "Konten",
      "description": "Kontenplan, Salden, Kontenblatt"
    },
    {
      "name": "Buchungen",
      "description": "Buchungen lesen, anlegen, stornieren"
    },
    {
      "name": "Rechnungen",
      "description": "Ein-/Ausgangsrechnungen lesen"
    },
    {
      "name": "Belege",
      "description": "Belege hochladen"
    },
    {
      "name": "Berichte",
      "description": "BWA, UStVA, Saldenliste"
    }
  ],
  "paths": {
    "/me": {
      "get": {
        "tags": [
          "Allgemein"
        ],
        "summary": "Verbindungstest & Mandanten-Info",
        "description": "Smoke-Test-Endpoint. Liefert Key-Scopes und Mandanten-Stammdaten. Scope: read. Liefert auch die Umgebung des Schlüssels (live oder sandbox).",
        "x-scope": "read",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "tenant": {
                      "type": "object",
                      "properties": {
                        "userId": {
                          "type": "string"
                        },
                        "companyName": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "taxNumber": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "vatId": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      }
                    },
                    "apiKey": {
                      "type": "object",
                      "properties": {
                        "keyId": {
                          "type": "string"
                        },
                        "permissions": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "umgebung": {
                          "type": "string",
                          "enum": [
                            "live",
                            "sandbox"
                          ],
                          "description": "Umgebung des Schlüssels. \"sandbox\" heißt: dieser Schlüssel arbeitet auf dem Testmandanten des Accounts und erreicht die echte Buchhaltung nie. Sandbox-Schlüssel beginnen mit epago_test_."
                        }
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "423": {
            "$ref": "#/components/responses/AccountLocked"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "operationId": "getMe"
      }
    },
    "/accounts": {
      "get": {
        "tags": [
          "Konten"
        ],
        "summary": "Kontenplan abrufen",
        "description": "Liefert den Kontenplan des Mandanten. Mit withBalances=1 zusaetzlich aktuelle Salden. Scope: read.",
        "x-scope": "read",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "withBalances",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ]
            },
            "description": "Salden mitliefern"
          },
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Saldenzeitraum von (nur mit withBalances)"
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Saldenzeitraum bis (nur mit withBalances)"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "accounts": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Account"
                      }
                    },
                    "period": {
                      "$ref": "#/components/schemas/Period"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "423": {
            "$ref": "#/components/responses/AccountLocked"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "operationId": "listAccounts"
      }
    },
    "/accounts/{number}/ledger": {
      "get": {
        "tags": [
          "Konten"
        ],
        "summary": "Kontenblatt abrufen",
        "description": "Buchungszeilen eines Kontos mit laufendem Saldo. Scope: read.",
        "x-scope": "read",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "number",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Kontonummer, z.B. 1200"
          },
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "accountNumber": {
                      "type": "string"
                    },
                    "entries": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/LedgerEntry"
                      }
                    },
                    "period": {
                      "$ref": "#/components/schemas/Period"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "423": {
            "$ref": "#/components/responses/AccountLocked"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "operationId": "getAccountLedger"
      }
    },
    "/journal-entries": {
      "get": {
        "tags": [
          "Buchungen"
        ],
        "summary": "Buchungen lesen",
        "description": "Buchungen des Mandanten, optional gefiltert nach Datum und Status. Scope: read.",
        "x-scope": "read",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Buchungsdatum von (inkl.)"
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Buchungsdatum bis (inkl.)"
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "posted",
                "reversed"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "entries": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/JournalEntry"
                      }
                    },
                    "filters": {
                      "type": "object"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "423": {
            "$ref": "#/components/responses/AccountLocked"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "operationId": "listJournalEntries"
      },
      "post": {
        "tags": [
          "Buchungen"
        ],
        "summary": "Buchung anlegen",
        "description": "Legt eine Buchung an. Laeuft durch DIESELBE Validierung wie die Oberflaeche: Soll=Haben (Toleranz 0,01 EUR), Steuerautomatik-Konsistenz (Steuerschluessel 1/2/3/8/9), Kontoexistenz, Periodensperre (festgeschriebene Perioden -> 409). Vermerkt source='api' im Audit-Trail. Scope: write.\n\nDer Body kommt in GENAU EINER von zwei Formen: mit fertigen Buchungszeilen (lines[]) oder vereinfacht (betrag, bruttoKonto, sachKonto). Beide zusammen ergeben 400.",
        "x-scope": "write",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/JournalEntryLinesInput"
                  },
                  {
                    "$ref": "#/components/schemas/JournalEntrySimpleInput"
                  }
                ]
              },
              "examples": {
                "barverkauf": {
                  "summary": "Barverkauf 119,00 EUR brutto (19 % USt), Zeilen fertig (SKR03)",
                  "value": {
                    "date": "2026-06-12",
                    "description": "Barverkauf",
                    "reference": "BV-001",
                    "status": "posted",
                    "lines": [
                      {
                        "accountNumber": "1000",
                        "debit": 119,
                        "credit": 0
                      },
                      {
                        "accountNumber": "8400",
                        "debit": 0,
                        "credit": 100,
                        "taxCode": "3"
                      },
                      {
                        "accountNumber": "1776",
                        "debit": 0,
                        "credit": 19,
                        "taxCode": "3"
                      }
                    ]
                  }
                },
                "barverkauf_vereinfacht": {
                  "summary": "Derselbe Barverkauf vereinfacht: Server baut die Zeilen (SKR03, Automatikkonto 8400)",
                  "value": {
                    "date": "2026-06-12",
                    "description": "Barverkauf",
                    "reference": "BV-001",
                    "status": "posted",
                    "betrag": 119,
                    "bruttoKonto": "1000",
                    "sachKonto": "8400"
                  }
                },
                "buerobedarf_vereinfacht": {
                  "summary": "Eingangsrechnung 119,00 EUR mit Steuerschluessel 9 (SKR03, Nicht-Automatikkonto)",
                  "value": {
                    "date": "2026-06-12",
                    "description": "Buerobedarf Muster GmbH",
                    "reference": "ER-2026-042",
                    "status": "posted",
                    "betrag": 119,
                    "bruttoKonto": "1600",
                    "sachKonto": "4930",
                    "steuerschluessel": "9"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Angelegt",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Die Felder zeilen, steuerschluessel, split und kontenrahmen stehen nur in der Antwort auf die vereinfachte Form: sie sagen, was der Server aus den Angaben gemacht hat.",
                  "properties": {
                    "entry": {
                      "$ref": "#/components/schemas/JournalEntry"
                    },
                    "zeilen": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/JournalEntryLineInput"
                      },
                      "description": "Die serverseitig gebauten Buchungszeilen."
                    },
                    "steuerschluessel": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Der effektiv angewandte BU-Schluessel (aus der Angabe oder vom Automatikkonto), null bei einer Buchung ohne Steuer."
                    },
                    "split": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "description": "Brutto, Netto und Steuer des Vorgangs.",
                      "properties": {
                        "brutto": {
                          "type": "number"
                        },
                        "netto": {
                          "type": "number"
                        },
                        "steuer": {
                          "type": "number"
                        }
                      }
                    },
                    "kontenrahmen": {
                      "type": "string",
                      "enum": [
                        "SKR03",
                        "SKR04"
                      ],
                      "description": "Der Kontenrahmen des Mandanten, gegen den gerechnet wurde."
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "423": {
            "$ref": "#/components/responses/AccountLocked"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "operationId": "createJournalEntry"
      }
    },
    "/journal-entries/{id}/reverse": {
      "post": {
        "tags": [
          "Buchungen"
        ],
        "summary": "Buchung stornieren",
        "description": "GoBD-konformes Storno: erzeugt eine Gegenbuchung mit aktuellem Datum und markiert das Original als 'reversed'. Loeschen ist nicht moeglich. Scope: write.",
        "x-scope": "write",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string",
                    "description": "Storno-Grund (wird auf der Storno-Buchung persistiert)"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Storniert",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "original": {
                      "$ref": "#/components/schemas/JournalEntry"
                    },
                    "reversal": {
                      "$ref": "#/components/schemas/JournalEntry"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "423": {
            "$ref": "#/components/responses/AccountLocked"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Interner Fehler — code: internal_error | reverse_incomplete (Gegenbuchung wurde bereits angelegt, Markierung des Originals schlug fehl — NICHT automatisch wiederholen)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "reverseJournalEntry"
      }
    },
    "/invoices": {
      "get": {
        "tags": [
          "Rechnungen"
        ],
        "summary": "Rechnungen lesen",
        "parameters": [
          {
            "name": "documentType",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "outgoing",
                "incoming"
              ]
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "paymentStatus",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "invoices": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Invoice"
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "423": {
            "$ref": "#/components/responses/AccountLocked"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "operationId": "listInvoices",
        "x-scope": "read",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ]
      }
    },
    "/invoices/{id}": {
      "get": {
        "tags": [
          "Rechnungen"
        ],
        "summary": "Einzelne Rechnung lesen",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "invoice": {
                      "$ref": "#/components/schemas/Invoice"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "423": {
            "$ref": "#/components/responses/AccountLocked"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "operationId": "getInvoice",
        "x-scope": "read",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ]
      }
    },
    "/invoices/{id}/payments": {
      "get": {
        "tags": [
          "Rechnungen"
        ],
        "summary": "Zahlungen eines Belegs lesen",
        "description": "Alle erfassten Zahlungen des Belegs, aelteste zuerst. Fremde oder unbekannte ID: 404. Scope: read.",
        "x-scope": "read",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "payments": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Payment"
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "423": {
            "$ref": "#/components/responses/AccountLocked"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "operationId": "listInvoicePayments"
      },
      "post": {
        "tags": [
          "Rechnungen"
        ],
        "summary": "Zahlung an einem Beleg erfassen",
        "description": "Erfasst eine Zahlung und laeuft dabei durch dieselbe Schreibschicht wie die Oberflaeche: Belegstatus-Gate (auf einen Entwurf oder einen stornierten Beleg geht keine Zahlung ein, 409), Ueberzahlungsschutz, Zahlungsbuchung, Umsatzsteuer-Umbuchung bei Ist-Versteuerung (Paragraf 20 UStG) und die Fortschreibung des Zahlungsstands am Beleg.\n\nBleibt nach der Zahlung ein Rest offen, ist zahlungsart PFLICHT. Fehlt sie, antwortet die API mit 400 zahlungsart_erforderlich und liefert restBetrag, vorschlag und die ausfuehrbaren optionen mit; der zweite Aufruf setzt dann die gewaehlte zahlungsart. Grund: eine Teilzahlung, ein Skontoabzug und ein Forderungsausfall sehen in den Daten gleich aus, haben aber verschiedene Folgen fuer die Umsatzsteuer (Paragraf 17 UStG kennt keine Bagatellgrenze).\n\nGoBD: Skonto und Forderungsausfall erzeugen eine EIGENE Buchung mit dem Zahlungsdatum (Paragraf 17 Abs. 1 Satz 7 UStG: Zeitraum der Aenderung). Die Rechnung und ihre Buchung bleiben unveraendert. Scope: write.",
        "x-scope": "write",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PaymentInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Erfasst",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "payment": {
                      "$ref": "#/components/schemas/Payment"
                    },
                    "invoice": {
                      "$ref": "#/components/schemas/Invoice"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "400": {
            "description": "Ungueltige Anfrage — code: bad_request | validation_error | zahlungsart_erforderlich | begruendung_erforderlich. Die beiden letzten tragen Vorschlag, Restbetrag und Optionen im Fehlerobjekt.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ZahlungsdifferenzError"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Auf diesen Beleg kann keine Zahlung erfasst werden (Entwurf, storniert, unbekannter Status) oder die Periode ist festgeschrieben — code: payment_not_allowed | period_closed | conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "423": {
            "$ref": "#/components/responses/AccountLocked"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "operationId": "createInvoicePayment"
      }
    },
    "/documents": {
      "post": {
        "tags": [
          "Belege"
        ],
        "summary": "Beleg hochladen",
        "description": "Laedt einen Beleg als Base64 hoch (max. 10 MB; PDF, Bilder, Excel, CSV). SHA256-Hash fuer GoBD-Integritaet wird serverseitig gebildet. Scope: write.",
        "x-scope": "write",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DocumentInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Hochgeladen",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "document": {
                      "$ref": "#/components/schemas/Document"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "423": {
            "$ref": "#/components/responses/AccountLocked"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "operationId": "uploadDocument"
      }
    },
    "/reports/bwa": {
      "get": {
        "tags": [
          "Berichte"
        ],
        "summary": "BWA (Betriebswirtschaftliche Auswertung)",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "423": {
            "$ref": "#/components/responses/AccountLocked"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "operationId": "getBwaReport",
        "x-scope": "read",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ]
      }
    },
    "/reports/vat": {
      "get": {
        "tags": [
          "Berichte"
        ],
        "summary": "UStVA (Umsatzsteuer-Voranmeldung)",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "423": {
            "$ref": "#/components/responses/AccountLocked"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "operationId": "getVatReport",
        "x-scope": "read",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ]
      }
    },
    "/reports/account-balances": {
      "get": {
        "tags": [
          "Berichte"
        ],
        "summary": "Saldenliste",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "423": {
            "$ref": "#/components/responses/AccountLocked"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "operationId": "getAccountBalancesReport",
        "x-scope": "read",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API-Schluessel als Bearer-Token: `Authorization: Bearer epago_…`"
      },
      "apiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key",
        "description": "Alternativ: API-Schluessel im x-api-key-Header."
      }
    },
    "schemas": {
      "Period": {
        "type": "object",
        "properties": {
          "from": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "to": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          }
        }
      },
      "Account": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "accountNumber": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "accountType": {
            "type": "string"
          },
          "chartOfAccounts": {
            "type": "string"
          },
          "balance": {
            "type": "number"
          },
          "totalDebit": {
            "type": "number"
          },
          "totalCredit": {
            "type": "number"
          },
          "transactionCount": {
            "type": "integer"
          }
        }
      },
      "LedgerEntry": {
        "type": "object",
        "properties": {
          "entryId": {
            "type": "string"
          },
          "entryNumber": {
            "type": "integer"
          },
          "entryDate": {
            "type": "string",
            "format": "date"
          },
          "description": {
            "type": "string"
          },
          "reference": {
            "type": [
              "string",
              "null"
            ]
          },
          "debit": {
            "type": "number"
          },
          "credit": {
            "type": "number"
          },
          "runningBalance": {
            "type": "number"
          },
          "status": {
            "type": "string"
          }
        }
      },
      "JournalEntryLineInput": {
        "type": "object",
        "required": [
          "accountNumber"
        ],
        "properties": {
          "accountNumber": {
            "type": "string",
            "description": "Kontonummer"
          },
          "accountName": {
            "type": "string",
            "description": "Optional; faellt auf accountNumber zurueck"
          },
          "debit": {
            "type": "number",
            "default": 0
          },
          "credit": {
            "type": "number",
            "default": 0
          },
          "taxCode": {
            "type": [
              "string",
              "null"
            ],
            "description": "Steuerschluessel 1/2/3/8/9 (DATEV)"
          },
          "costCenterId": {
            "type": [
              "string",
              "null"
            ]
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "JournalEntryLine": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "accountNumber": {
            "type": "string"
          },
          "accountName": {
            "type": "string"
          },
          "debit": {
            "type": "number"
          },
          "credit": {
            "type": "number"
          },
          "taxCode": {
            "type": [
              "string",
              "null"
            ]
          },
          "lineOrder": {
            "type": "integer"
          }
        }
      },
      "JournalEntry": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "entryNumber": {
            "type": "integer"
          },
          "date": {
            "type": "string",
            "format": "date"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "description": {
            "type": "string"
          },
          "reference": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "posted",
              "reversed"
            ]
          },
          "source": {
            "type": "string",
            "enum": [
              "ui",
              "api",
              "ai",
              "import"
            ]
          },
          "reversalOfId": {
            "type": [
              "string",
              "null"
            ]
          },
          "reversedById": {
            "type": [
              "string",
              "null"
            ]
          },
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/JournalEntryLine"
            }
          }
        }
      },
      "Invoice": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "invoiceNumber": {
            "type": "string"
          },
          "documentType": {
            "type": "string",
            "enum": [
              "outgoing",
              "incoming"
            ]
          },
          "documentDate": {
            "type": "string",
            "format": "date"
          },
          "status": {
            "type": "string"
          },
          "paymentStatus": {
            "type": "string"
          },
          "totalGross": {
            "type": "number"
          },
          "totalNet": {
            "type": "number"
          },
          "totalTax": {
            "type": "number"
          }
        }
      },
      "DocumentInput": {
        "type": "object",
        "required": [
          "fileName",
          "originalName",
          "mimeType",
          "data"
        ],
        "properties": {
          "fileName": {
            "type": "string"
          },
          "originalName": {
            "type": "string"
          },
          "mimeType": {
            "type": "string",
            "enum": [
              "application/pdf",
              "image/jpeg",
              "image/png",
              "image/gif",
              "image/webp",
              "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
              "application/vnd.ms-excel",
              "text/csv"
            ]
          },
          "size": {
            "type": "integer",
            "description": "Optional; wird sonst aus data abgeleitet"
          },
          "category": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "documentDate": {
            "type": "string",
            "format": "date"
          },
          "reference": {
            "type": "string"
          },
          "data": {
            "type": "string",
            "description": "Base64-kodierter Dateiinhalt"
          }
        }
      },
      "Document": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "fileName": {
            "type": "string"
          },
          "originalName": {
            "type": "string"
          },
          "mimeType": {
            "type": "string"
          },
          "size": {
            "type": "integer"
          },
          "category": {
            "type": "string"
          },
          "hash": {
            "type": "string",
            "description": "SHA256 (GoBD-Integritaet)"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "description": "Maschinenlesbarer Fehlercode"
              },
              "message": {
                "type": "string",
                "description": "Menschenlesbare Beschreibung (Deutsch)"
              }
            }
          }
        }
      },
      "PaymentInput": {
        "type": "object",
        "required": [
          "amount",
          "paymentDate"
        ],
        "properties": {
          "amount": {
            "type": "number",
            "description": "Vereinnahmter Betrag in Euro. Nicht 0,00 und nicht mehr als der offene Rest des Belegs. Negativ = Rueckzahlung."
          },
          "paymentDate": {
            "type": "string",
            "format": "date",
            "description": "Vereinnahmungsdatum (YYYY-MM-DD): Tag der Gutschrift auf dem Konto, nicht die Wertstellung und nicht das Erfassungsdatum."
          },
          "paymentMethod": {
            "type": "string",
            "enum": [
              "bank_transfer",
              "cash",
              "card",
              "paypal",
              "direct_debit",
              "other"
            ],
            "description": "Zahlungsweg. Bestimmt das Geldkonto der Zahlungsbuchung (Bank, Kasse, PSP). Vorgabe: bank_transfer."
          },
          "reference": {
            "type": "string",
            "description": "Verwendungszweck oder eigene Referenz."
          },
          "zahlungsart": {
            "type": "string",
            "enum": [
              "vollzahlung",
              "teilzahlung",
              "skonto",
              "forderungsausfall"
            ],
            "description": "Wie ist eine Differenz zum offenen Rest zu lesen? PFLICHT, sobald nach dieser Zahlung ein Rest offen bleibt. Fehlt sie dann, antwortet die API mit 400 zahlungsart_erforderlich und liefert Vorschlag, Restbetrag und Optionen mit — geraten wird nichts (Paragraf 17 UStG haengt eine Steuerberichtigung daran)."
          },
          "zahlungsartBegruendung": {
            "type": "string",
            "description": "Begruendung des Mandanten. Pflicht bei forderungsausfall (UStAE 17.1 Abs. 5 Satz 2) und bei einem skonto, das von der Vereinbarung am Beleg abweicht (Frist abgelaufen, kein Skonto vereinbart, Betrag hoeher)."
          }
        }
      },
      "ZahlungsdifferenzOption": {
        "type": "object",
        "description": "Eine ausfuehrbare Option fuer den offenen Rest. Die zahlungsart dieser Option kann unveraendert als Feld zahlungsart in einen erneuten Aufruf gesetzt werden.",
        "properties": {
          "code": {
            "type": "string",
            "enum": [
              "skonto_buchen",
              "rest_offen_lassen",
              "forderungsausfall_buchen"
            ]
          },
          "zahlungsart": {
            "type": "string",
            "enum": [
              "vollzahlung",
              "teilzahlung",
              "skonto",
              "forderungsausfall"
            ]
          },
          "text": {
            "type": "string"
          },
          "moeglich": {
            "type": "boolean",
            "description": "false, wenn fuer den Steuerfall des Belegs kein Weg gebaut ist — dann sagt hinweis warum."
          },
          "hinweis": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "Payment": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "invoiceId": {
            "type": "string"
          },
          "paymentDate": {
            "type": "string",
            "format": "date"
          },
          "amount": {
            "type": "number"
          },
          "paymentMethod": {
            "type": "string"
          },
          "reference": {
            "type": "string",
            "nullable": true
          },
          "journalEntryId": {
            "type": "string",
            "nullable": true,
            "description": "Buchungssatz der Zahlung. null, wenn nicht gebucht wurde (Beleg noch nicht gebucht, oder der Vorgang ist bereits ueber den Bankabgleich gebucht)."
          },
          "zahlungsart": {
            "type": "string",
            "nullable": true,
            "description": "Nur gesetzt, wenn diese Zahlung einen Rest offen gelassen hat."
          },
          "differenzBetrag": {
            "type": "number",
            "nullable": true
          },
          "differenzEntschieden": {
            "type": "string",
            "nullable": true,
            "enum": [
              "gebucht",
              "offen"
            ]
          },
          "buchungsbefund": {
            "type": "object",
            "description": "Wurde die Zahlung gebucht, und wenn nicht: warum, und was kann der Mandant tun. Nur in der Antwort auf POST enthalten.",
            "properties": {
              "gebucht": {
                "type": "boolean"
              },
              "grund": {
                "type": "string"
              },
              "meldung": {
                "type": "string",
                "nullable": true
              },
              "kiFrage": {
                "type": "string",
                "nullable": true
              },
              "handlungsoption": {
                "type": "string",
                "nullable": true
              }
            }
          },
          "entgeltminderung": {
            "type": "object",
            "description": "Was mit einer Differenz geschehen ist (Paragraf 17 UStG). Nur in der Antwort auf POST enthalten.",
            "properties": {
              "art": {
                "type": "string",
                "enum": [
                  "ausgeglichen",
                  "offen",
                  "rueckzahlung"
                ]
              },
              "restBetrag": {
                "type": "number"
              },
              "zahlungsart": {
                "type": "string",
                "enum": [
                  "vollzahlung",
                  "teilzahlung",
                  "skonto",
                  "forderungsausfall"
                ]
              },
              "journalEntryId": {
                "type": "string",
                "nullable": true,
                "description": "Buchung der Entgeltminderung. null, wenn keine noetig war (Ist-Versteuerung oder ungebuchter Beleg) — dann steht der Grund in korrekturEntfaellt."
              },
              "korrekturEntfaellt": {
                "type": "string",
                "nullable": true
              }
            }
          }
        }
      },
      "ZahlungsdifferenzError": {
        "type": "object",
        "required": [
          "error"
        ],
        "description": "400-Antwort, wenn nach der Zahlung ein Rest offen bleibt und die zahlungsart fehlt (code: zahlungsart_erforderlich) oder eine Begruendung fehlt (code: begruendung_erforderlich). Der Fehler traegt alles mit, was fuer den zweiten, entschiedenen Aufruf noetig ist.",
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "zahlungsart_erforderlich",
                  "begruendung_erforderlich"
                ]
              },
              "message": {
                "type": "string"
              },
              "grund": {
                "type": "string",
                "description": "zahlungsart_fehlt | vollzahlung_mit_rest | kein_rest — bzw. bei begruendung_erforderlich der Grund der Skontopruefung."
              },
              "restBetrag": {
                "type": "number",
                "description": "Was nach dieser Zahlung offen bliebe, in Euro."
              },
              "vorschlag": {
                "type": "string",
                "enum": [
                  "vollzahlung",
                  "teilzahlung",
                  "skonto",
                  "forderungsausfall"
                ],
                "description": "Vorschlag von epago, KEINE Entscheidung. forderungsausfall wird nie vorgeschlagen."
              },
              "optionen": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ZahlungsdifferenzOption"
                }
              },
              "kiFrage": {
                "type": "string",
                "nullable": true
              },
              "handlungsoption": {
                "type": "string",
                "nullable": true
              }
            }
          }
        }
      },
      "JournalEntryLinesInput": {
        "type": "object",
        "required": [
          "date",
          "description",
          "lines"
        ],
        "properties": {
          "date": {
            "type": "string",
            "format": "date"
          },
          "description": {
            "type": "string"
          },
          "reference": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "posted"
            ],
            "default": "draft"
          },
          "costCenterId": {
            "type": [
              "string",
              "null"
            ]
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "lines": {
            "type": "array",
            "minItems": 2,
            "items": {
              "$ref": "#/components/schemas/JournalEntryLineInput"
            }
          }
        },
        "description": "Buchung mit fertigen Buchungszeilen (Experten-Form). Soll gleich Haben, mindestens zwei Zeilen."
      },
      "JournalEntrySimpleInput": {
        "type": "object",
        "description": "Vereinfachte Form: der Server baut die Buchungszeilen inklusive Steuer-Split ueber dieselbe Funktion wie die Stapelerfassung der Oberflaeche, mit dem Kontenrahmen des Mandanten (SKR03 oder SKR04). Welche Kontonummer ein Automatikkonto ist, entscheidet damit der Kontenstamm und nicht der Aufrufer.",
        "required": [
          "date",
          "description",
          "betrag",
          "bruttoKonto",
          "sachKonto"
        ],
        "properties": {
          "date": {
            "type": "string",
            "format": "date"
          },
          "description": {
            "type": "string"
          },
          "reference": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "posted"
            ],
            "default": "draft"
          },
          "costCenterId": {
            "type": [
              "string",
              "null"
            ]
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "betrag": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "Bruttobetrag, immer positiv."
          },
          "bruttoKonto": {
            "type": "string",
            "description": "Kontonummer, die den Bruttobetrag traegt (Geld- oder Personenkonto)."
          },
          "bruttoKontoName": {
            "type": "string",
            "description": "Nur fuer die Lesbarkeit der Buchungszeile."
          },
          "sachKonto": {
            "type": "string",
            "description": "Kontonummer des Sachkontos (Erloes oder Aufwand). Traegt den Nettobetrag; die Steuerzeile setzt der Server daneben."
          },
          "sachKontoName": {
            "type": "string",
            "description": "Nur fuer die Lesbarkeit der Buchungszeile."
          },
          "steuerschluessel": {
            "type": "string",
            "enum": [
              "1",
              "2",
              "3",
              "8",
              "9"
            ],
            "description": "BU-Schluessel: 1 steuerfrei, 2 USt 7 %, 3 USt 19 %, 8 VSt 7 %, 9 VSt 19 %. Weglassen, wenn eines der beiden Konten ein Automatikkonto ist - eine zusaetzliche Angabe wird dann abgewiesen."
          },
          "seite": {
            "type": "string",
            "enum": [
              "S",
              "H"
            ],
            "description": "Soll oder Haben des Kontos aus bruttoKonto. Bei den Schluesseln 2, 3, 8 und 9 leitet der Server die Richtung aus dem Steuerfall ab; anzugeben ist sie bei Schluessel 1 (steuerfrei), bei einer Buchung ganz ohne Steuer und fuer die Gegenrichtung (Gutschrift, Warenruecksendung). Fehlt sie, wo sie gebraucht wird, antwortet die API mit 400 statt zu raten."
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Ungueltige Anfrage (Validierung) — code: bad_request | validation_error; bei POST /documents zusaetzlich: file_too_large | unsupported_media_type",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "API-Schluessel fehlt/ungueltig/abgelaufen/widerrufen — code: missing_api_key | invalid_api_key | api_key_expired | api_key_revoked",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Forbidden": {
        "description": "Scope fehlt (z.B. read-Key auf Schreib-Route) — code: insufficient_scope",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "Ressource nicht gefunden (oder fremder Mandant) — code: not_found",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Conflict": {
        "description": "Regelkonflikt, z.B. festgeschriebene Periode oder nicht stornierbar — code: period_closed | conflict | cannot_reverse",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Rate-Limit ueberschritten (max. rate_limit_per_minute Anfragen/Minute, gezaehlt ueber alle Instanzen) — code: rate_limit_exceeded. Retry-After nennt die Wartezeit in Sekunden.",
        "headers": {
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          },
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "PaymentRequired": {
        "description": "Die Public API gehoert nicht zum Tarif des Mandanten, oder der Zugang ist wegen einer offenen Zahlung gesperrt — code: feature_locked | account_gesperrt",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "AccountLocked": {
        "description": "Der Mandant hat die Loeschung seiner Daten beantragt; bis zur Ruecknahme wird nicht mehr verarbeitet — code: konto_gesperrt_loeschantrag",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "headers": {
      "RateLimitLimit": {
        "description": "Erlaubte Anfragen im laufenden Fenster (eine Minute). Der Wert steht am API-Schluessel; ein Sandbox-Schluessel traegt einen kleineren.",
        "schema": {
          "type": "integer",
          "example": 60
        }
      },
      "RateLimitRemaining": {
        "description": "Noch freie Anfragen im laufenden Fenster. Nie negativ.",
        "schema": {
          "type": "integer",
          "example": 59
        }
      },
      "RateLimitReset": {
        "description": "Unix-Zeit in SEKUNDEN, zu der wieder Kapazitaet frei wird.",
        "schema": {
          "type": "integer",
          "example": 1700000060
        }
      },
      "RetryAfter": {
        "description": "Sekunden, die der Aufrufer warten soll (RFC 9110, 10.2.3). Nur an der 429-Antwort, immer mindestens 1.",
        "schema": {
          "type": "integer",
          "example": 42
        }
      }
    }
  }
}
