# Das Belegschema

`GET /public/v1/files/{file_id}/document` und `GET /public/v1/documents/{document_id}` liefern einen Beleg als **`ocost.document.v1`**, in den Begriffen der EN 16931 (der E-Rechnungsnorm hinter XRechnung, ZUGFeRD und Peppol UBL). Jeder Wert nennt seinen Business Term (BT-…) oder seine Gruppe (BG-…) und lässt sich so auf UBL oder CII abbilden.

Das Format ist stabil: Eine inkompatible Änderung kommt als `ocost.document.v2` unter `/public/v2`, v1 bleibt noch 12 Monate erreichbar.

## Konventionen

- **Beträge und Mengen:** Dezimalzeichenketten wie gedruckt (`"1270.92"`, `"2.5"`), nie Zahlen. Nutzen Sie einen Dezimaltyp.
- **Sätze:** Prozent-Zeichenketten (`"19"`, `"7"`, `"0"`, `"5.5"`).
- **Daten** nach ISO 8601 (`2026-11-03`), **Währungen** nach ISO 4217 (`EUR`), **Länder** nach ISO 3166-1 alpha-2 (`DE`).
- **Einheiten:** UNECE-Empfehlung 20 (`C62` eins, `H87` Stück, `HUR` Stunde, `KGM` Kilogramm) und Verpackungen der Empfehlung 21 mit ihrem `X` (`XCT` Karton, `XPF` Palette). Unbekannt ist `null`, nie `C62`.
- **Fehlt etwas, ist es `null`:** nicht gedruckt oder verworfen, weil Ocost nicht sicher war. Ocost rät nie.

## Die Felder

| Feld | EN 16931 | Bedeutung |
| --- | --- | --- |
| `schema` | | Immer `"ocost.document.v1"`. |
| `id`, `file_id` | | Der Beleg und seine Datei. |
| `pages` | | Seine Seiten in der Datei, ab 1. |
| `kind` | | `invoice` (jede zu zahlende Rechnung: Abschlags-, Schluss-, Anzahlungs-, Sammel-, Dauerrechnung; `type_code` sagt, welche), `credit_note`, `invoice_correction`, `receipt`, `pro_forma`, `delivery_note`, `order`, `order_confirmation`, `quote`, `contract` (Miet-, Versicherungs-, sonstige Verträge), `statement` (Abrechnung eines Factors), `payment_reminder`, `other`. `kind_detail` ist die genaue Art, zur Information. |
| `direction` | | `incoming` (die Rechnung eines Lieferanten), `outgoing` (Ihr Unternehmen hat ihn ausgestellt) oder `unsure`. Ermittelt aus USt-IdNr., Steuernummer und Namen unter [Einstellungen › Unternehmen](/settings/company): Halten Sie sie aktuell. |
| `type_code` | BT-3 | UNCL 1001: `380` Rechnung, `381` Gutschrift, `384` korrigierte Rechnung, `326` Abschlagsrechnung, `386` Anzahlungsrechnung, `389` Gutschriftsverfahren, `325` Proforma. |
| `number`, `issue_date`, `due_date` | BT-1, BT-2, BT-9 | Nummer, Rechnungsdatum, Fälligkeit. |
| `delivery_date` | BT-72 | Lieferdatum. |
| `invoicing_period_start`, `invoicing_period_end` | BT-73, BT-74 | Der abgerechnete Zeitraum. |
| `valid_until` | | Bei einem Angebot. |
| `currency` | BT-5 | Währung. |
| `buyer_reference` | BT-10 | Referenz des Käufers. |
| `order_reference`, `order_references` | BT-13 | Die Bestellung und jede genannte Bestellung. |
| `preceding_invoice` | BT-25 | Worauf sich eine Gutschrift oder Korrektur bezieht. |
| `seller`, `buyer` | BG-4, BG-7 | Je: `name` (BT-27, BT-44), `vat_id` (BT-31, BT-48), `tax_registration_id` (BT-32, die Steuernummer), `legal_registration_id` (BT-30, BT-47, der Registereintrag), `address` (`street`, `postal_code`, `city`, `country`), `contact` (`email`, `phone`), `website`. |
| `payee` | BG-10 | Wer bezahlt wird, wenn nicht der Verkäufer (ein Factor). |
| `deliver_to` | BG-15 | Die Lieferanschrift. |
| `other_parties[]` | | Jede weitere genannte Partei, mit `role` (`invoicee`, `service_recipient`, `payer`, `ship_to`, `carrier`, `debtor`, `landlord`, `tenant`, `contractor`, …) und `printed_role` (wie gedruckt). |
| `payment` | BG-16 | `means_code` (BT-81, UNCL 4461: `58` SEPA-Überweisung, `59` SEPA-Lastschrift, `48` Karte), `means` (als Wort oder `already_paid`), `iban` (BT-84), `bic` (BT-86), `terms` (BT-20 wie gedruckt), `net_days` (Tage bis zur vollen Zahlung ab Rechnungsdatum), `paid_on` (der Tag, an dem er laut Beleg bezahlt wurde). |
| `early_payment_discounts[]` | BT-20 | Skonto (`#SKONTO#` der XRechnung): `days`, `percent`, `base_amount` (null: vom fälligen Betrag), `due_date` (Rechnungsdatum plus `days`). |
| `totals` | BG-22 | `net` (BT-109), `tax` (BT-110), `gross` (BT-112), `payable` (BT-115, noch zu zahlen; null, wenn es der Bruttobetrag ist), `prices_include_vat` (wie auf einer Quittung). |
| `vat_breakdown[]` | BG-23 | `category` (BT-118, UNCL 5305: `S` Regelsatz, `Z` Nullsatz, `E` steuerfrei, `AE` Reverse Charge, `K` innergemeinschaftlich, `G` Ausfuhr, `O` nicht steuerbar), `rate` (BT-119), `taxable_amount` (BT-116), `tax_amount` (BT-117), `exemption_reason` (BT-120). |
| `lines[]` | BG-25 | `id` (BT-126, die Position), `name` (BT-153), `seller_item_id` (BT-155, Artikelnummer), `standard_item_id` (BT-157, GTIN), `quantity` (BT-129), `unit` (BT-130), `price` (BT-146, netto), `base_quantity` (BT-149, für wie viele Einheiten `price` gilt; null: eine), `net_amount` (BT-131), `discount_percent`, `vat_category` (BT-151), `vat_rate` (BT-152); wo gedruckt, `ordered_quantity`, `backordered_quantity`, `batch`, `pzn`, `attributes` (Farbe, Größe, Material, Marke, …). |
| `deductions[]` | | Die früheren Rechnungen, die eine Schlussrechnung abzieht: `number`, `issue_date`, `net`, `tax`, `gross`. |
| `card` | | Für einen Beleg ohne eigene Felder (ein Prüfbericht, ein Brief): `title`, `summary`, `facts` (`label`, `kind`, `date`, `amount`, `currency`, `number`, `text`, `page`), Fristen zuerst. |
| `worked_out[]` | | Ein Betrag, den Ocost ausgerechnet hat, weil der gelesene nicht stimmen kann: `field`, `printed`, `rule`. |
| `sources` | | Je Wert, nach Pfad (`number`, `totals.gross`, `seller.vat_id`, …): `page`, `quote` (die gelesenen Worte), `confidence` und bei Zweifel `why`. |
| `checks[]` | | Jede Prüfung, die Ocost im Code ausgeführt hat: `name`, `passed`, `sums` (eine Summenprüfung), `fields` (Pfade), `at` (der Satz oder die Position). |
| `tax_check` | | Eine Eingangsrechnung nach § 14 UStG: `case` (`standard`, `small`, `reverse_charge`, `intra_eu`, `exempt`) und `missing[]` (`item`, `law`, `at_stake`: Fehlt es, kostet es den Vorsteuerabzug). Null für Arten, die nicht geprüft werden. |
| `attention[]` | | Worauf ein Mensch vor dem Buchen schauen sollte: `sums` (eine Summe stimmt nicht), `outgoing`, `possible_duplicate`, `self_billed` (Gutschriftsverfahren). |
| `possible_duplicate_of` | | Ein früherer Beleg derselben Art und Nummer vom selben Verkäufer, sonst null. |
| `read_with` | | `e_invoice` (genau aus dem XML) oder `reader` (aus den Seiten). |

`confidence` in `sources`:

- `checked`: Eine Prüfung hat den Wert bestätigt, oder er stammt aus einer E-Rechnung.
- `read`: Nichts spricht dagegen.
- `doubt`: Eine Prüfung schlug fehl (`why` nennt sie), oder seine Worte stehen nicht auf seiner Seite.

Die Summenprüfungen in `checks` sind `total`, `vat`, `vat_net`, `vat_tax`, `gross`, `payable`, `signs`, `skonto`, `deduction`, `deductions` und `quantity`. **Füllen Sie eine Rechnung ohne Blick eines Menschen nur, wenn jede Prüfung mit `sums: true` bestanden ist.**

## Ein Beispiel

Eine Eingangsrechnung mit zwei Positionen zu 19 %, 2 % Skonto innerhalb von 14 Tagen:

```json
{
  "schema": "ocost.document.v1",
  "id": "01a1b08c-7aa1-7b52-9d4e-2f1f1c0c4f11",
  "file_id": "01a1b08c-6e3a-7562-8f4a-7924722667b1",
  "pages": [1],
  "kind": "invoice",
  "kind_detail": "invoice",
  "direction": "incoming",
  "type_code": "380",
  "number": "RE-2026-1187",
  "issue_date": "2026-11-03",
  "due_date": "2026-12-03",
  "delivery_date": "2026-10-30",
  "invoicing_period_start": null,
  "invoicing_period_end": null,
  "valid_until": null,
  "currency": "EUR",
  "buyer_reference": null,
  "order_reference": "PO-2316",
  "order_references": ["PO-2316"],
  "preceding_invoice": null,
  "seller": {
    "name": "Musterbüro Lieferant GmbH",
    "vat_id": "DE123456789",
    "tax_registration_id": null,
    "legal_registration_id": "HRB 12345",
    "address": { "street": "Beispielweg 4", "postal_code": "50667", "city": "Köln", "country": "DE" },
    "contact": { "email": "rechnung@lieferant.example", "phone": null },
    "website": null
  },
  "buyer": {
    "name": "Beispiel Handel GmbH",
    "vat_id": "DE987654321",
    "tax_registration_id": null,
    "legal_registration_id": null,
    "address": { "street": "Hauptstraße 1", "postal_code": "40213", "city": "Düsseldorf", "country": "DE" },
    "contact": null,
    "website": null
  },
  "payee": null,
  "deliver_to": null,
  "other_parties": [],
  "payment": {
    "means_code": "58",
    "means": "transfer",
    "iban": "DE02120300000000202051",
    "bic": null,
    "terms": "14 Tage 2 % Skonto, 30 Tage netto",
    "net_days": 30,
    "paid_on": null
  },
  "early_payment_discounts": [
    { "days": 14, "percent": "2", "base_amount": null, "due_date": "2026-11-17" }
  ],
  "totals": { "net": "1068.00", "tax": "202.92", "gross": "1270.92", "payable": null, "prices_include_vat": false },
  "vat_breakdown": [
    { "category": "S", "rate": "19", "taxable_amount": "1068.00", "tax_amount": "202.92", "exemption_reason": null }
  ],
  "lines": [
    {
      "id": "1", "name": "Bürostuhl ErgoPlus schwarz", "seller_item_id": "ES-200", "standard_item_id": null,
      "quantity": "4", "unit": "H87", "price": "189.00", "base_quantity": null, "net_amount": "756.00",
      "discount_percent": null, "vat_category": "S", "vat_rate": "19",
      "ordered_quantity": null, "backordered_quantity": null, "batch": null, "pzn": null,
      "attributes": { "colour": "schwarz" }
    },
    {
      "id": "2", "name": "Montage", "seller_item_id": null, "standard_item_id": null,
      "quantity": "4", "unit": "HUR", "price": "78.00", "base_quantity": null, "net_amount": "312.00",
      "discount_percent": null, "vat_category": "S", "vat_rate": "19",
      "ordered_quantity": null, "backordered_quantity": null, "batch": null, "pzn": null,
      "attributes": {}
    }
  ],
  "deductions": [],
  "card": null,
  "worked_out": [],
  "sources": {
    "number": { "page": 1, "quote": "RE-2026-1187", "confidence": "read", "why": null },
    "totals.gross": { "page": 1, "quote": "1.270,92 €", "confidence": "checked", "why": null },
    "seller.vat_id": { "page": 1, "quote": "DE123456789", "confidence": "checked", "why": null }
  },
  "checks": [
    { "name": "vat", "passed": true, "sums": true, "fields": ["totals.net", "totals.tax"], "at": "19" },
    { "name": "gross", "passed": true, "sums": true, "fields": ["totals.net", "totals.tax", "totals.gross"], "at": null }
  ],
  "tax_check": { "case": "standard", "missing": [] },
  "attention": [],
  "possible_duplicate_of": null,
  "read_with": "reader"
}
```

## Auslesen

```python
from decimal import Decimal

document = session.get(f"{API}/files/{file_id}/document", timeout=15).json()
sums_hold = all(c["passed"] for c in document["checks"] if c["sums"])
gross = Decimal(document["totals"]["gross"]) if document["totals"]["gross"] else None
doubts = [path for path, s in document["sources"].items() if s["confidence"] == "doubt"]
```

```javascript
const document = await fetch(`${API}/documents/${documentId}`, { headers: auth }).then((r) => r.json());
const sumsHold = document.checks.filter((c) => c.sums).every((c) => c.passed);
// Beträge sind Zeichenketten: als solche behalten oder mit einer Dezimal-Bibliothek rechnen.
console.log(document.number, document.totals.gross, sumsHold);
```
