bimetricsEntwickler
Rezepte

Beleg hochladen und auslesen

Einen Beleg ohne Doppelungen hochladen, den Stand der Erkennung abfragen und die erkannten Daten lesen.

Als Markdown ansehen

Achtung

Erzeugt einen echten Beleg, verbraucht Kontingent und wird GoBD-protokolliert.

Probiere das Rezept deshalb mit einer Firma und einem Beleg aus, die du dafür vorgesehen hast. Die Regeln für den Upload stehen ausführlich unter Beleg-Upload.

Ablauf

  1. Hochladen: Die Datei geht als multipart/form-data an POST /api/document/, mit einer eigenen ID im Header X-Upload-Request-Id. Die Antwort enthält den neuen Beleg mit seiner id.
  2. Stand abfragen: GET /api/document/{id} liefert den Beleg mit dem Stand der Erkennung in uploadDocument.ocrStatus. Du fragst alle paar Sekunden ab, bis der Stand success, failed oder quota_paused ist.
  3. Ergebnis lesen: Bei success stehen die erkannten Daten im Beleg, etwa Rechnungsnummer, Rechnungsdatum, Aussteller und Positionen.

Code

Du brauchst einen Schlüssel mit der Berechtigung upload für den Upload und read für die Abfragen, gespeichert in der Umgebungsvariable BIMETRICS_API_KEY. Die Beispiele laufen mit curl und jq, mit Node.js ab Version 20 oder mit Python und dem Paket requests. Das JavaScript-Beispiel läuft nur als ES-Modul, weil es import und await außerhalb von Funktionen nutzt: Speichere es als beleg-upload.mjs oder setze in deiner package.json "type": "module".

#!/usr/bin/env bash
set -euo pipefail

API="https://app.bimetrics.de/api"
AUTH="Authorization: Bearer $BIMETRICS_API_KEY"
# Eine eigene ID je Beleg, zum Beispiel die Belegnummer in deinem System.
# Bei einer Wiederholung bleibt sie gleich.
UPLOAD_ID="wawi-2026-000123"

# 1. Hochladen. --retry wiederholt bei Zeitüberschreitung, 429, 500, 502,
#    503 und 504 mit derselben ID und wartet dabei die Zeit aus Retry-After ab.
id=$(curl --fail --silent --show-error --retry 5 "$API/document/" \
  -H "$AUTH" \
  -H "X-Upload-Request-Id: $UPLOAD_ID" \
  -F "auto[]=@rechnung.pdf" | jq -r '.[0].id')
echo "Beleg $id"

# 2. Stand abfragen, alle 5 Sekunden, höchstens 10 Minuten lang
status=""
for _ in $(seq 120); do
  status=$(curl --fail --silent --show-error --retry 5 "$API/document/$id" -H "$AUTH" \
    | jq -r '.uploadDocument.ocrStatus')
  case "$status" in success | failed | quota_paused) break ;; esac
  sleep 5
done
echo "Stand der Erkennung: $status"

# 3. Ergebnis lesen
if [ "$status" = "success" ]; then
  curl --fail --silent --show-error --retry 5 "$API/document/$id" -H "$AUTH" | jq '{
    kind,
    invoiceNumber,
    invoiceDate,
    issuer: .issuer.name,
    totalCents: ([.invoicePositions[]?.total] | add)
  }'
fi

Die Upload-ID

  • Verwende für jeden neuen Beleg eine neue ID, am besten eine, die es in deinem System schon gibt, etwa die Belegnummer. Dann kannst du einen abgebrochenen Lauf später mit derselben ID fortsetzen.
  • Bei einer Wiederholung bleiben ID, Datei, Dateiname und Feld gleich. Die API liefert dann denselben Beleg zurück, auch wenn die erste Anfrage schon angekommen war.
  • Dieselbe ID mit einer anderen Datei ergibt 409 mit dem Code UploadRequestConflict. Das wiederholst du nicht.

Liegt dieselbe Datei in der Firma schon als Beleg vor, legt bimetrics keinen zweiten an. Die Antwort enthält dann den vorhandenen Beleg und "duplicate": true.

Stand der Erkennung

uploadDocument.ocrStatusWas tun
pending, processingWeiter abfragen. Nach einem vorübergehenden Fehler kann der Stand von processing wieder auf pending gehen.
successErgebnis lesen.
failedDer Beleg ist gespeichert, die Daten wurden aber nicht erkannt. Prüfe ihn in der App.
quota_pausedDer Beleg ist gespeichert, das Kontingent ist ausgeschöpft. bimetrics setzt die Erkennung fort, sobald wieder Kontingent frei ist. Lade den Beleg nicht erneut hoch.
  • Frage höchstens alle paar Sekunden ab. Jede Abfrage zählt zu den Limits.
  • Bleibt der Stand länger bei pending oder processing, brich nach einer Obergrenze ab und frage später wieder ab. Der Beleg ist in jedem Fall gespeichert.
  • Lädst du viele Belege hoch, frage den Stand gesammelt über POST /api/document/filter ab, etwa mit uploadDocument.ocrStatus und createdAt, statt jeden Beleg einzeln.

Das Ergebnis

Die erkannten Daten stehen direkt im Beleg (gekürzt, Werte erfunden):

{
  "id": "0b5c2f4e-8a1d-4c3e-9f6a-2d7b1e8c4a90",
  "kind": "receipt",
  "invoiceNumber": "RE-2026-0917",
  "invoiceDate": { "year": 2026, "month": 9, "day": 17 },
  "dueDate": { "year": 2026, "month": 10, "day": 1 },
  "issuer": { "name": "Schreinerei Albrecht GmbH", "iban": "DE89370400440532013000" },
  "recipient": { "name": "Mustermann Bau GmbH" },
  "invoicePositions": [
    { "label": "Einbauschrank Eiche", "total": 238000, "vatPercentage": "19" },
    { "label": "Montage", "total": 35700, "vatPercentage": "19" }
  ],
  "uploadDocument": { "ocrStatus": "success" }
}
  • total ist der Bruttobetrag der Position in Cent, vatPercentage der Steuersatz in Prozent. Der Bruttobetrag des Belegs ist die Summe der total-Werte, hier 2.737,00 €. Mehr unter Datentypen.
  • Mit auto[] kann sich kind bei der Erkennung ändern, etwa auf invoice, wenn die Rechnung von der eigenen Firma stammt (siehe Glossar).
  • Die Originaldatei rufst du mit GET /api/document/{id}/content ab.

Auf dieser Seite