Zum Inhalt springen
Rechnungskit
Live-Demo

Eigene Anwendung per API anbinden

Aktualisiert am

Mit der REST-API meldet deine eigene Anwendung (SaaS, eigener Checkout, Plattform) Transaktionen an Rechnungskit. Diese Seite erklärt Schlüssel, Endpunkte, die wichtigsten Felder und was du beim Senden beachten musst. Die vollständige Referenz findest du auf rechnungskit.de/docs/api.

Der Ablauf in Kurzform: Deine Anwendung schickt die Bestellung. Die Zahlung kommt vom angebundenen Zahlungsanbieter, und sobald beides zusammenpasst, erstellt Rechnungskit den Beleg. Bei Ware wartet die Rechnung zusätzlich auf deine Versandmeldung.

REST-API · Ablauf
Von der API-Bestellung zur E-Rechnung
Deine Anwendung
POST /v1/ordersPositionen und Beträge in Cent, optional paymentRef
POST /v1/fulfillmentsnur bei physischer Ware, setzt das Leistungsdatum
Rechnungskit
  1. Nimmt die Bestellung anidempotent über deine orderNo
  2. Ordnet die Zahlung zuvom angebundenen Zahlungsanbieter, paymentRef beschleunigt das
  3. Erstellt den Belegmit EN-16931-Validierung und PDF
Ergebnis
E-RechnungZUGFeRD, mit gültiger Leitweg-ID als XRechnung 3.0
Abruf per APIStatus über GET /v1/orders/{orderNo}, PDF und XML über /v1/invoices
Ein „bezahlt“-Feld gibt es nicht: Den Zahlungsstatus liefert der Zahlungsanbieter. Einzige Ausnahme ist eine Bestellung, die komplett mit Empfehlungsguthaben bezahlt wurde.

Grundlagen

Punkt So ist es
Basis-URL https://api.rechnungskit.de/v1
Format REST mit JSON, OpenAPI 3.1
Authentifizierung Bearer-Token mit deinem API-Schlüssel
Beträge immer in ganzzahligen Cent, nie als Kommazahlen
Idempotenz Deine Bestellnummer (orderNo) ist der natürliche Schlüssel, zusätzlich wird der Idempotency-Key-Header unterstützt. Doppelte Übermittlungen erzeugen keine doppelten Belege.

API-Schlüssel

Unter Verbindungen → API erstellst du Schlüssel. Es gibt zwei Berechtigungen:

Berechtigung Wofür
orders:write Bestellungen, Stornierungen und Fulfillments melden, Guthaben-Bewegungen und Salden senden
invoices:read Belege abrufen, auch den Stand einer Bestellung über GET /v1/orders/{orderNo}
subscriptions:write Links ins Kundenportal für ein Abo erzeugen. Beim Anlegen des Schlüssels nicht vorausgewählt, weil ein solcher Link das Beenden des Vertrags erlaubt

Es gibt EINEN Schlüssel-Typ (Präfix rk_key_). Ob Belege Testdaten oder echt sind, entscheidet allein der Testmodus des Projekts, nie der Schlüssel. Derselbe Schlüssel erzeugt im Testmodus Testbelege und nach dem Go-Live echte Belege, nichts muss ausgetauscht werden. Ältere Schlüssel mit rk_test_- oder rk_live_-Präfix bleiben unverändert gültig.

  • Einmal sichtbar: Der volle Schlüssel wird genau einmal angezeigt, danach nur noch der Anfang.
  • Ablauf: wählbar, 30, 90 oder 365 Tage oder ohne Ablauf.
  • Unbenutzt: Schlüssel, die über 90 Tage nicht benutzt wurden, werden markiert.

Wichtigste Endpunkte

Endpunkt Wofür Berechtigung
POST /v1/orders Bestellung übermitteln (Positionen, Beträge in Cent, Zahlart) orders:write
GET /v1/orders/{orderNo} Stand einer Bestellung samt aller Belege invoices:read
POST /v1/orders/{orderNo}/cancel Stornierung einer Bestellung melden (siehe unten) orders:write
POST /v1/fulfillments Versand melden (setzt das Leistungsdatum), nur für physische Ware nötig orders:write
POST /v1/checkout-links Einmal-Link zum Abo-Checkout, mit vorausgefüllter E-Mail und deinen Metadaten (siehe unten) orders:write
POST /v1/subscriptions/{id}/portal-links Kurzlebiger Link ins Kundenportal für ein Abo (siehe unten) subscriptions:write
GET /v1/invoices Belege auflisten invoices:read
GET /v1/invoices/{reference} einen Beleg abrufen invoices:read
GET /v1/invoices/{reference}/pdf und /xml Beleg-Dateien laden invoices:read

Beispiel-Bestellung

POST https://api.rechnungskit.de/v1/orders
Authorization: Bearer rk_key_…

{
  "orderNo": "CM-10482",
  "email": "kunde@example.com",
  "paymentRef": "pi_123",
  "requiresShipping": true,
  "shippingGrossCents": 490,
  "billingAddress": { "name": "Erika Muster", "country_code": "DE" },
  "items": [
    { "name": "Kaffee 1kg", "quantity": 2, "unitGrossCents": 2490, "taxRate": 7, "productId": "bohne-42" }
  ]
}

Pflicht sind orderNo, billingAddress mit country_code und mindestens eine Position mit name, quantity und unitNetCents oder unitGrossCents.

unitNetCents ist der Nettopreis, unitGrossCents der Bruttopreis. taxRate ist optional: Fehlt er, bestimmt Rechnungskit den Satz aus dem Produkt und rechnet die Umsatzsteuer bei unitNetCents obendrauf, bei unitGrossCents heraus.

Versandkosten

Versandkosten schickst du auf einem von zwei Wegen:

  • shippingGrossCents: der bezahlte Bruttobetrag. Rechnungskit ermittelt die Steuer selbst.
  • shippingNetCents plus shippingTaxRate: wenn dein Checkout die Versandsteuer schon berechnet hat.

Bei gemischten Steuersätzen im Warenkorb teilt Rechnungskit den Versand automatisch nach dem Brutto-Warenwert auf (BFH, Urteil vom 22.01.2025, XI R 22/22: Aufteilung nach Bruttoverkaufspreisen), genau wie beim Rechnungskit-Checkout. Der bezahlte Betrag ändert sich dabei nie.

Belege einer Bestellung abrufen

GET /v1/orders/{orderNo} liefert je Beleg:

  • den Typ (invoice, correction, voucher_receipt) und die Belegart,
  • den Bezug auf die korrigierte Rechnung (referencedInvoiceId),
  • Datum, Bruttobetrag und PDF-Link.

Was GET /v1/invoices/{reference} zurückgibt, hängt davon ab, womit du fragst:

Du fragst mit Du bekommst
UUID oder Belegnummer genau diesen Beleg
Bestellnummer oder Zahlungsreferenz die Rechnung der Bestellung, auch wenn es inzwischen eine Stornorechnung gibt

Die Stornorechnung holst du über ihre UUID. Bei Bestellungen, die du per API schickst, steht sie in GET /v1/orders/{orderNo}. Bei Bestellungen aus einer Shop-Anbindung (WooCommerce, Shopify, Shopware …) nimmst du GET /v1/invoices?orderNo={orderNo}: Die Liste enthält jeden Beleg der Bestellung, neueste zuerst. type sagt, was er ist (invoice, correction für die Stornorechnung, voucher_receipt), documents.pdf ist sein PDF-Link (mit API-Schlüssel abrufen). Eine Seite hat bis zu 100 Belege (limit=100, Standard 25). Ist pagination.hasMore wahr, holst du die nächste Seite mit cursor={pagination.nextCursor}.

Ist dein Kunde in deiner Software angemeldet, fordert dein Server den Link zum Abo-Checkout an, statt einen festen Link anzuzeigen:

POST /v1/checkout-links
{
  "plan": "abo-pro",
  "email": "kunde@firma.de",
  "metadata": { "workspace_id": "ws_42" },
  "returnUrl": "https://app.deine-software.de/abrechnung"
}
→ 201 { "data": { "id": "…", "url": "https://checkout.rechnungskit.de/s/abo-pro?t=…", "expiresAt": "…" } }
  • plan ist der Teil nach /s/ im Abo-Link des Plans (oder die Plan-Id).
  • In der URL steht nur ein zufälliger Token, keine E-Mail und keine Metadaten. Der Käufer kann nichts davon ändern.
  • Die E-Mail ist im Checkout vorausgefüllt, der Käufer kann sie noch ändern.
  • metadata (bis 20 Einträge, nur Text) landet am Abo und kommt in jedem subscription.*-Webhook zurück, für die ganze Laufzeit: bei Verlängerung, Upgrade, Downgrade, Mengenänderung, Kündigung und Zahlungsproblemen, auch wenn der Käufer das im Kundenportal ändert. So weiß deine Software immer, welcher Workspace gemeint ist. Namen, die Rechnungskit selbst nutzt (etwa migrated_from), lehnt die API ab.
  • returnUrl: Nach dem erfolgreichen Kauf leitet die Danke-Seite dorthin weiter, mit ?subscription=<Abo-Id> am Ende (dieselbe Id wie subscriptionId im Webhook). Ohne returnUrl gilt die Weiterleitung am Plan: in der App unter Produkte → Abo-Plan bearbeiten → „Nach dem Kauf weiterleiten zu“.
  • Der Link gilt 24 Stunden (expiresInMinutes, 5 Minuten bis 7 Tage). Bis das Abo aktiv ist, lässt er sich erneut öffnen, etwa nach einer abgebrochenen Zahlung. Danach und nach Ablauf zeigt er einen Hinweis und, wenn gesetzt, einen Link zurück zu returnUrl.
  • Dein bestehender API-Schlüssel mit orders:write reicht. Einen Idempotency-Key berücksichtigt der Endpunkt, er ist hier aber nicht Pflicht.

Schick die E-Mail nie als Parameter im öffentlichen Abo-Link mit: Sie stünde dann in Browserverlauf und Server-Logs.

Nur Geschäftskunden

Verkaufst du laut AGB nur an Unternehmen, stell in der App unter Produkte am Abo-Plan den Kundenkreis auf „Nur Geschäftskunden“. Der Checkout zeigt dann keine Auswahl „Privatperson“, der Firmenname ist Pflicht, und Käufer aus anderen EU-Ländern müssen ihre USt-IdNr. angeben. Rechnungskit prüft das auch auf dem Server, ein angepasster Aufruf kommt also nicht daran vorbei. Die Einstellung gilt für alle neuen Abschlüsse des Plans, mit und ohne API-Link. Laufende Abos bleiben unverändert.

Hat dein Kunde bei dir ein Kundenkonto, schickst du ihn ohne zweite Anmeldung zu seinem Abo im Kundenportal. Klickt er bei dir auf einen Button, fordert dein Server den Link an und leitet ihn weiter:

POST /v1/subscriptions/{id}/portal-links
{ "intent": "withdraw" }
→ 201 { "data": { "url": "https://konto.rechnungskit.de/subscription/…?s=…&intent=withdraw", "expiresAt": "…" } }
  • {id} ist die subscriptionId aus den subscription.*-Webhooks und der ?subscription=-Parameter deiner returnUrl.
  • Der Schlüssel braucht den Scope subscriptions:write. Bestehende Schlüssel haben ihn nicht, lege dafür einen neuen an.
  • Fordere den Link erst beim Klick an und nur für einen Kunden, den du selbst als Inhaber dieses Abos angemeldet hast. Wer den Link hat, kann den Vertrag beenden.
  • Der Link gilt 15 Minuten (expiresInMinutes, 1 bis 60). Danach zeigt er „Link abgelaufen“, mit der Möglichkeit, sich einen neuen Link per E-Mail schicken zu lassen.
  • Kündigung und Widerruf laufen dann im Kundenportal. Dein Formular entfällt, und du erfährst das Ergebnis über subscription.canceled und subscription.withdrawn.
Dein Button intent Was dein Kunde sieht
Abo verwalten ohne die ganze Abo-Seite
Verträge hier kündigen cancel nur die Bestätigung mit „Kündigung bestätigen“
Vertrag widerrufen withdraw nur die Abfrage mit „Widerruf bestätigen“

Ist die Widerrufsfrist abgelaufen, öffnet withdraw die normale Abo-Seite mit einem Hinweis darauf. Eine API zum direkten Auslösen von Kündigung oder Widerruf gibt es nicht. Beides bestätigt der Kunde im Kundenportal, einen Widerruf per E-Mail oder Brief erfasst du in der App. Ob dein Button und seine Platzierung den Anforderungen von § 312k BGB und § 356a BGB genügen, lass im Zweifel anwaltlich prüfen.

Artikel-Id je Position: productId

Jede Position darf eine productId tragen, die stabile Artikel-Id aus deinem System. Sie ist optional, aber empfohlen: Sie bindet die Position an genau ein Produkt im Rechnungskit-Katalog, auch wenn sich Name oder SKU später ändern.

Ohne productId sucht Rechnungskit das Produkt zuerst über die SKU und, wenn auch die fehlt, über den exakten Namen. Änderst du dann dein SKU-Schema oder lieferst eine SKU nach, entsteht je Variante ein neues Katalogprodukt. Das fällt zuerst dort auf, wo Produkte gepflegt werden: Dieselbe Ware steht mehrfach in der Liste, und jede Fassung braucht eigene USt-Kategorie und Umsatzrealisierung.

Die Id ist beliebiger Text (zum Beispiel die Primärschlüssel-Id deiner Artikeltabelle). Wichtig ist nur, dass sie je Artikel gleich bleibt. Verschiedene productId bedeuten verschiedene Artikel, auch bei identischem Namen. So bleiben echte Varianten sauber getrennt.

SaaS und digitale Leistungen: nur ein Endpunkt

Für SaaS oder rein digitale Leistungen genügt POST /v1/orders. Das Feld requiresShipping lässt du einfach weg (Standard false). Dann gibt es keine Erfüllungs-Wartestufe, und /v1/fulfillments wird nie aufgerufen.

Die Rechnung entsteht, sobald die Zahlung des angebundenen Zahlungsanbieters zur Bestellung passt. paymentRef mitschicken beschleunigt die Zuordnung. Ein „bezahlt“-Feld gibt es bewusst nicht: Den Zahlungsstatus liefert der Zahlungsanbieter, nicht der Sender.

Leistungszeitraum (SaaS/Abos): das Feld service_period

Für zeitbasierte Leistungen (SaaS, Abos) sendest du den Leistungszeitraum im optionalen Feld service_period. Achtung, hier gilt snake_case, nicht camelCase.

Regel Details
Aufbau Objekt mit start_date und end_date, jeweils als ISO-Datum YYYY-MM-DD
Vollständig entweder beide Schlüssel oder keiner, fehlt einer, kommt 422
Reihenfolge end_date darf nicht vor start_date liegen

Beispiel im Bestell-Body:

"service_period": { "start_date": "2026-06-01", "end_date": "2026-06-30" }
service_period · Fälle
Was mit dem Leistungszeitraum passiert
Mitgeschickt oder am Produkt hinterlegt
Steuert die pRAP (nur Bilanzierer) und steht als Leistungszeitraum auf der ZUGFeRD-Rechnung. Ein mitgeschickter Zeitraum hat Vorrang vor dem Abrechnungsrhythmus am SaaS-Produkt
Rechnungsofort
Weggelassen, keine Voreinstellung
Bei EÜR passiert nichts weiter. Bei Bilanzierung hält Rechnungskit die Rechnung als Aufgabe „Produkt ohne Umsatzrealisierung“ an
Bilanzerst nach dem Nachtragen
Beginn im früheren Wirtschaftsjahr
Bei Bilanzierung entsteht die Aufgabe „Leistungszeitraum im Vorjahr“, als Hinweis, keine Blockade
Rechnungtrotzdem sofort
Bei EÜR wird sofort vereinnahmt, eine Rechnungsabgrenzung gibt es dort nicht.

Wozu der Zeitraum dient

Der Leistungszeitraum steuert die passive Rechnungsabgrenzung (pRAP, nur Bilanzierer; bei EÜR wird sofort vereinnahmt) und landet als Leistungszeitraum auf der ZUGFeRD-Rechnung. Ein hier mitgeschickter service_period hat Vorrang vor der SaaS-Voreinstellung am Produkt.

Lässt du das Feld weg, greift die Produkt-Voreinstellung (z. B. der am SaaS-Produkt hinterlegte Abrechnungsrhythmus). Der Checkout-Link (/c/…) kennt kein Feld dafür, dort kommt der Zeitraum aus der Produktkonfiguration.

Rückdatierter Leistungszeitraum

Ein start_date, das VOR dem Rechnungsdatum liegt, ist erlaubt. Liegt der Beginn im selben Wirtschaftsjahr, ist das unproblematisch, das Jahresergebnis stimmt.

Beginnt der Zeitraum in einem FRÜHEREN, meist abgeschlossenen Wirtschaftsjahr, legt Rechnungskit (bei Bilanzierung) eine Aufgabe „Leistungszeitraum im Vorjahr“ an. Die bereits vergangenen Monate gehören periodengerecht ins Vorjahr, ggf. ist eine Bilanzberichtigung nötig. Die Rechnung wird trotzdem erstellt, die Aufgabe ist ein Hinweis, keine Blockade.

Fehlender Leistungszeitraum bei zeitbasierten Produkten

Schickst du keinen service_period und ist am Produkt auch keine SaaS-Voreinstellung (Abrechnungszeitraum) hinterlegt, hängt es von der Gewinnermittlung ab:

  • EÜR: Es wird sofort vereinnahmt, es passiert nichts weiter.
  • Bilanzierung: Rechnungskit braucht den Zeitraum für die Rechnungsabgrenzung. Die Rechnung wird dann NICHT sofort erstellt, sondern als Aufgabe „Produkt ohne Umsatzrealisierung“ unter Zu prüfen gehalten. Es wird noch keine Rechnungsnummer vergeben.

Du legst dann die Umsatzrealisierung am Produkt fest (Produkte → Alle Produkte) oder schickst den service_period an der Bestellung nach. Danach wird die Rechnung automatisch erstellt.

Zahlarten über die API

Die Zuordnung von Zahlungen läuft über die Zahlungsanbieter-Anbindungen (Stripe, Mollie, PayPal, Unzer), deine Anwendung liefert die Bestelldaten. Für Kauf auf Rechnung muss das Feld paymentGateway exakt einem unter Verbindungen → Connect hinterlegten Rechnungskauf-Gateway entsprechen. Die Schreibweise zählt.

Überweisung über Stripe (paymentGateway stripe_transfer)

Soll dein Kunde per Überweisung zahlen und das Geld über dein Stripe-Konto ankommen, sendest du die Bestellung mit paymentGateway „stripe_transfer“ und einer E-Mail-Adresse.

stripe_transfer · Ablauf
Überweisung über dein Stripe-Konto
  1. Deine AnwendungBestellung mit stripe_transferplus E-Mail-Adresse
  2. RechnungskitRechnung und ZahlungsauftragLeistung sofort, Ware mit der Versandmeldung
  3. Dein KundeÜberweistan die virtuelle Stripe-IBAN, mit Stripes Referenz als Verwendungszweck
  4. StripeOrdnet zuRechnungskit schließt den offenen Posten
Es entsteht keine Stripe-Rechnung, also auch keine Invoicing-Gebühr, nur die Gebühr für die Überweisung.

So läuft es ab:

  1. Bei einer Leistung entsteht die Rechnung sofort, bei Ware erst mit der Versandmeldung über /v1/fulfillments.
  2. Gleichzeitig legt Rechnungskit in deinem Stripe-Konto einen Zahlungsauftrag für Banküberweisung an.
  3. Auf der Rechnung stehen die virtuelle Stripe-IBAN deines Kunden und, als Verwendungszweck, Stripes Referenz für genau diese Zahlung.
  4. Kommt die Überweisung mit dieser Referenz an, ordnet Stripe das Geld zu, und Rechnungskit schließt den offenen Posten unter /receivables.

Ist die Rechnung bezahlt?

GET /v1/invoices/{reference} und GET /v1/invoices liefern je Beleg einen Block payment mit status (open, partially_paid, paid oder uncollectible), paidCents und openCents. Bei Kauf auf Rechnung und Vorkasse folgt er dem offenen Posten: Er springt auf paid, sobald du die Zahlung in der App unter Offene Posten bestätigst oder eine zugeordnete Überweisung eingeht. Eine Rechnung zu einer Zahlung beim Zahlungsanbieter ist von Anfang an paid. Bei Gutschriften ist der Block null. totals.dueCents ist dagegen der Betrag, der auf der Rechnung steht, und ändert sich nach der Ausstellung nie. Deine Software fragt also payment.status ab, um etwa einen Zugang freizuschalten.

Fehlt einem Produkt die Umsatzrealisierung oder die Leistungsart, antwortet POST /v1/fulfillments mit 200 und invoicesCreated: 0 sowie held.reason: recognition_missing. Die Versandmeldung ist gespeichert, in der App entsteht eine Aufgabe, und sobald das Produkt eingestuft ist, entsteht die Rechnung von selbst. Lässt sich die Umsatzsteuer aus einem anderen Grund nicht bestimmen, lautet der Grund held.reason: tax_blocked, held.message nennt ihn, und die Bestellung wartet als Aufgabe in der App. Eine wiederholte Versandmeldung löst die Rechnung erneut aus, legt aber nie eine zweite an.

Weil keine Stripe-Rechnung entsteht, fällt keine Invoicing-Gebühr an, nur die für die Überweisung. Als Überweisungsbetrag gilt das Bestellbrutto, und die Rechnung trifft ihn auf den Cent.

Dafür braucht der Stripe-Schlüssel die Rechte Customers (Schreiben) und Payment Intents (Schreiben). Fehlt eines, entsteht keine Rechnung. Stattdessen bekommst du eine dringende Aufgabe samt E-Mail, und nach dem Ergänzen holt Rechnungskit die Rechnung automatisch nach. Wird der Zahlungsauftrag in Stripe abgebrochen, bevor bezahlt ist, folgt eine Stornorechnung.

Rechnung zuerst, Einzug danach (paymentGateway stripe_debit)

Soll die Rechnung vor dem Einzug stehen und deine Anwendung löst die Zahlung über Stripe selbst aus, sendest du die Bestellung mit paymentGateway „stripe_debit“. Karte und SEPA-Lastschrift laufen beide so.

  1. POST /v1/orders mit paymentGateway „stripe_debit“ und grossTotalCents. Bei einer Leistung entsteht die Rechnung sofort, bei Ware erst mit der Versandmeldung über /v1/fulfillments.
  2. Auf der Rechnung steht „Der Betrag wird automatisch über das hinterlegte Zahlungsmittel eingezogen. Bitte nicht überweisen.“ Deine IBAN steht nicht darauf und ist dafür auch nicht nötig. Der offene Posten bleibt bis zur Zahlung offen, payment.status ist open.
  3. Du löst den PaymentIntent selbst aus und verknüpfst ihn: Trag die Bestellnummer in die Metadaten ein (metadata.orderNo) oder schick die PaymentIntent-ID als paymentRef. Das geht auch später, indem du die Bestellung noch einmal sendest.
  4. Meldet Stripe die Zahlung als erfolgreich, schließt Rechnungskit den offenen Posten. Eine zweite Rechnung entsteht nicht, und Rechnungskit schickt keine Zahlungserinnerungen.

Passt grossTotalCents nicht zu den Positionen, entsteht keine Rechnung, sondern eine Aufgabe. Eine Erstattung in Stripe erzeugt die Stornorechnung. Eine Rücklastschrift öffnet den Posten wieder und erzeugt eine Aufgabe, aber keinen Storno. Ein neuer Einzug mit derselben Bestellnummer schließt ihn dann.

Für den Einzug braucht Rechnungskit keine zusätzlichen Stripe-Rechte. Den Zahlungsauftrag legt deine Anwendung an.

Empfehlungsguthaben an der Bestellung: creditRedemptions

Hat dein Kunde für eine Bestellung Empfehlungsguthaben eingesetzt, schickst du den Betrag im optionalen Feld creditRedemptions mit. Es ist eine Liste von Objekten:

Feld Inhalt
type „referral_credit“
amountCents ganze Cent, größer 0
externalRef optional, die Id der Einlösung in deinem System
"creditRedemptions": [{ "type": "referral_credit", "amountCents": 1000, "externalRef": "tx_81" }]

Mit dem Feld ist grossTotalCents Pflicht, und die Summe der Einlösungen darf den Bruttobetrag nicht übersteigen. Sonst antwortet die API mit 422.

Guthaben ist eine Zahlung, kein Rabatt

Die Rechnung weist die Umsatzsteuer auf den vollen Betrag aus, der Guthabenanteil gilt als bereits bezahlt. Die Zahlungsart nennt beide Anteile, etwa „PayPal (37,00 €) und Empfehlungsguthaben (12,00 €)“. Dein Zahlungsanbieter zieht nur den Rest ein. Rabatte gehören weiter in discountNetCents.

Ist die Bestellung komplett mit Guthaben bezahlt, gibt es keine Zahlung beim Anbieter. Dann schickst du financialStatus „paid“, und die Summe der Einlösungen entspricht grossTotalCents. Rechnungskit stellt die Rechnung dann ohne Zahlungseingang aus, bei Ware erst mit der Versandmeldung.

Das Guthaben selbst führt dein Shop. Rechnungskit spiegelt es im Guthabenbuch unter Gutscheine für die Verbindlichkeit und die DATEV-Buchungen und zieht die Einlösung dort vom ältesten Saldo des Kunden ab. Fehlt dort ein passender Saldo, bleibt die Rechnung richtig, und unter Zu prüfen erscheint eine Aufgabe „Empfehlungsguthaben ohne Saldo“.

Guthaben per Push: credit-transactions

Verwaltet deine Anwendung Guthaben selbst, etwa Empfehlungsprämien, meldest du jede Bewegung an POST /v1/credit-transactions, bis zu 500 je Aufruf: { "transactions": [ ... ] }.

Feld Pflicht Inhalt
id ja deine eindeutige Kennung
type ja earn, redeem, reverse, payout oder expire
email ja E-Mail-Adresse des Kunden
amount_cents ja Betrag in Cent
occurred_at ja Zeitpunkt der Bewegung
delta_cents bei reverse Veränderung in Cent
order_ref, reverses_id, payout_ref nein Bezug auf Bestellung, zurückgenommene Bewegung und Auszahlung
hc_booking_date nein das Datum, an dem deine Buchhaltung den Vorgang bisher bucht

Ein wiederholter Aufruf legt nichts doppelt an. Die Antwort nennt neue (accepted) und schon bekannte (duplicates) Transaktionen.

Einmal am Tag meldest du die Salden an PUT /v1/credit-balances:

{ "balances": [{ "email": "...", "balance_cents": 1250 }] }

Rechnungskit vergleicht sie mit dem Verlauf. Weicht ein Kunde ab, entsteht eine Aufgabe, so fällt eine verlorene Meldung auf. Beide Endpunkte brauchen den Scope orders:write. Gebucht wird erst ab dem Stichtag, den du unter Gutscheine festlegst. Die Anleitung steht unter Guthaben aus dem eigenen Shop übernehmen.

Guthabenverlauf lesen lassen (Pull)

Statt zu pushen kann dein Shop den Verlauf bereitstellen, und Rechnungskit liest ihn selbst ab. Dafür stellt dein Shop zwei lesende Endpunkte bereit, geschützt mit einem Token:

Endpunkt in deinem Shop Liefert
GET <adresse>/referral-transactions?since=<cursor>&limit=<n> { "items": [...], "next_cursor": "...", "has_more": true }
GET <adresse>/referral-balances?as_of=<YYYY-MM-DD> den Saldo je Kunde als [{ "email": "...", "balance_cents": 1250 }]

Jeder Eintrag im Verlauf hat id, type (earn, redeem, reverse, payout oder expire), email, amount_cents, occurred_at und optional delta_cents (mit Vorzeichen), order_ref, reverses_id, payout_ref und hc_booking_date. Einträge ändern sich nie, eine Korrektur ist ein neuer Eintrag.

Einrichten und anstoßen

Adresse, Token und Stichtag trägst du unter Gutscheine im Abschnitt „Guthaben aus deinem eigenen Shop“ ein. Die Schritt-für-Schritt-Anleitung mit Probelauf und Stichtag steht unter Guthaben aus dem eigenen Shop übernehmen.

Rechnungskit liest stündlich ab der zuletzt gelesenen Stelle weiter und gleicht danach je Kunde ab: Saldo im Shop gegen die Summe des Verlaufs. Weicht ein Kunde ab, siehst du ihn dort in einer Liste.

Schneller geht es mit einem Anstoß: POST /api/v1/referral-credits/poke mit deinem API-Schlüssel, ohne Inhalt. Rechnungskit liest dann sofort. Geht ein Anstoß verloren, liest der nächste stündliche Lauf trotzdem alles.

Probelauf und Stichtag

Ohne Stichtag ist das ein Probelauf: Rechnungskit liest und gleicht ab, bucht aber nichts. Ab dem Stichtag übernimmt Rechnungskit den Verlauf ins Guthabenbuch. Zum Stichtag legt es je Kunde einen Eröffnungssaldo aus dem bisherigen Verlauf an, ohne Buchung, denn bis dahin hat dein Shop selbst gebucht.

Danach bucht Rechnungskit über die Kontenrollen unter Einstellungen → DATEV:

Bewegung Buchung
Verdienst Aufwand an Verbindlichkeit
Rücknahme umgekehrt
Auszahlung an die Bank
Verfall als Ertrag
Erstattung aufs Guthaben Debitor an Verbindlichkeit

Eine Einlösung bucht Rechnungskit über die Rechnung (Feld creditRedemptions), nicht über den Verlauf. Guthaben, das vor dem Stichtag angelegt und erst danach verfügbar wurde, bucht Rechnungskit nicht noch einmal, wenn hc_booking_date vor dem Stichtag liegt.

Ab dem Stichtag vergleicht der Abgleich den Saldo im Shop mit dem Guthabenbuch. Einlösungen, deren Rechnung noch fehlt, rechnet er heraus. Weicht etwas ab, entsteht eine Aufgabe. Fehlt eine Kontenrolle, erscheint sie im DATEV-Export als fehlende Zuordnung.

Öffentliche Auftraggeber: leitweg_id

Die Rechnungsadresse (billingAddress) nimmt neben vat_id auch leitweg_id an. Ist eine gültige Leitweg-ID gesetzt, wird die Rechnung dieser Bestellung als XRechnung 3.0 erstellt, sonst als ZUGFeRD. Eine formal ungültige Leitweg-ID beantwortet die API mit 422.

Voraussetzung ist eine Telefonnummer im Rechnungskontakt unter Einstellungen. Ohne sie hält Rechnungskit die XRechnung als Aufgabe an.

Testen

Nutze deinen normalen API-Schlüssel im Testmodus. Es ist die gleiche Pipeline, EN-16931-Validierung und PDF-Erstellung inklusive, aber:

  • Testnummernkreis (TEST-...),
  • keine Abrechnung und kein Versand,
  • kein Einfluss auf DATEV, OSS und ZM.

Es gilt Fair Use, Lasttests bitte nicht. Mehr dazu unter Testmodus (Sandbox).

Stornierte Bestellungen melden

Storniert dein Shop eine Bestellung, meldest du das über POST /v1/orders/{orderNo}/cancel, optional mit reason und cancelledAt. Was dann passiert, hängt vom Stand der Bestellung ab.

Stornierung · Antworten
Was die Stornierung je nach Stand bewirkt
Noch kein Beleg
Voll erstattet: Zahlung und Erstattung werden als „storniert + erstattet“ abgelegt, ohne Aufgabe. Liegt das Geld noch bei dir, entsteht eine Aufgabe, bis die Erstattung kommt
Ergebnisnie ein Beleg
Beleg existiert schon
Nichts ändert sich. Die Korrektur meldest du über POST /v1/orders/{orderNo}/refunds oder, bei Zahlung über einen Zahlungsanbieter, durch die Erstattung dort
Antwort409 order_invoiced
Bestellung unbekannt
Nie übermittelt? Dann POST /v1/orders mit status „cancelled“ und paymentRef
Antwort404 order_not_found
Beide Aufrufe darfst du beliebig oft wiederholen.
Stand der Bestellung Was passiert
noch kein Beleg Die Bestellung wird storniert und bekommt nie einen Beleg.
kein Beleg, Zahlung vollständig erstattet Rechnungskit legt Zahlung und Erstattung als „storniert + erstattet“ ab. Weil nie ein Umsatz entstand, gibt es weder Beleg noch Aufgabe.
kein Beleg, das Geld liegt noch bei dir Es entsteht eine Aufgabe, und eine spätere Erstattung legt den Vorgang von selbst ab.
Beleg existiert bereits Die Schnittstelle antwortet mit 409 order_invoiced und ändert nichts. Bei Kauf auf Rechnung und Vorkasse meldest du die Korrektur über POST /v1/orders/{orderNo}/refunds. Lief die Zahlung über einen Zahlungsanbieter, erstattest du dort, und die Stornorechnung entsteht automatisch.
Bestellung unbekannt Der Stornierungs-Aufruf antwortet mit 404 order_not_found.

Für eine Bestellung, die vor dem Zahlungsabschluss storniert und deshalb nie übermittelt wurde, schickst du POST /v1/orders mit "status": "cancelled" und der paymentRef. Darüber findet Rechnungskit die Zahlung samt automatischer Erstattung und legt beide ab. Beide Aufrufe darfst du beliebig oft wiederholen.

Erstattungen und Preisnachlässe melden (refunds)

Bei Kauf auf Rechnung und Vorkasse per Überweisung meldet kein Zahlungsanbieter je eine Erstattung. Die Quelle ist dann dein System. Gibt es nach der Rechnung eine Rücksendung, einen Preisnachlass oder eine volle Rücknahme, meldest du das über POST /v1/orders/{orderNo}/refunds (Berechtigung orders:write). Rechnungskit erstellt daraus eine Stornorechnung, genau wie bei Erstattungen aus Shops und von Zahlungsanbietern.

{
  "refundId": "BV-1042-R1",
  "reason": "Rücksendung 1 Flasche Spätburgunder (Korkfehler)",
  "items": [{ "productId": "spaetburgunder-2022", "quantity": 1 }],
  "refundedAt": "2026-11-10"
}
Feld Bedeutung
refundId Pflicht. Deine eigene Id der Erstattung, eindeutig je Projekt. Derselbe Aufruf noch einmal liefert 200 mit replayed: true und dieselben Belege. Dieselbe refundId mit anderem Inhalt beantwortet die API mit 409 refund_id_conflict.
reason Pflicht, 20 bis 500 Zeichen. Steht als Grund auf der Stornorechnung, wie im Storno-Dialog der App.
amountGrossCents Bruttobetrag der Erstattung in Cent. Allein verteilt er sich anteilig auf die Rechnungspositionen.
items Positionsgenau: je Eintrag genau eine Referenz (line = Position auf der Rechnung ab 1, productId aus deinem Bestell-Push, sku oder name) und quantity. Ohne Betrag ist die Erstattung genau so viel wert wie diese Positionen auf der Rechnung. Mit Betrag zusätzlich darf er höchstens die Positionen plus die ganzen Versandkosten abdecken.
refundedAt Datum der Erstattung, Standard heute. Es wird das Datum der Stornorechnung, nicht in der Zukunft und nicht vor dem Rechnungsdatum.
alreadyPaidBack Nur bei schon bezahlten Rechnungen: Ist das Geld schon zurücküberwiesen? Das steht dann auf der Stornorechnung.
invoiceNumber Nur nötig, wenn die Bestellung mehrere Rechnungen hat.

Die Antwort (201) enthält beide Belege mit Nummer, Link und PDF, in denselben Feldern wie invoices[] bei GET /v1/orders/{orderNo}: original und correction, dazu remainingGrossCents (was noch korrigiert werden kann) und openItem (der offene Posten danach). Einen eigenen Webhook für Stornorechnungen gibt es nicht: Die Antwort enthält schon beide Belege, und GET /v1/orders/{orderNo} listet sie ebenfalls. Die Stornorechnung geht nicht automatisch per E-Mail an deinen Kunden. Den PDF-Link gibst du selbst weiter.

refunds · Wirkung
Was eine gemeldete Erstattung bewirkt
Rechnung noch offen
Der offene Posten sinkt um die Erstattung. Ist alles erstattet, schließt er als „storniert“. Decken die bisherigen Teilzahlungen den Rest, springt er auf „bezahlt“ und invoice.paid geht raus
Kunde zahltnur noch den Rest
Rechnung schon bezahlt
Der offene Posten bleibt bezahlt, die Erstattung wird vermerkt. Das Geld überweist du selbst zurück, alreadyPaidBack hält fest, ob das schon passiert ist
Du zahlstzurück
Bezahlt über einen Zahlungsanbieter
Erstatte dort. Die Stornorechnung entsteht aus der Erstattung automatisch, ein zweiter Weg würde doppelt korrigieren
Antwort409 refund_via_provider
In allen Fällen bleibt die Originalrechnung unverändert. Die Stornorechnung erscheint im DATEV-Export wie jeder andere Storno.
Antwort Bedeutung
404 order_not_found Keine API-Bestellung mit dieser orderNo.
404 invoice_not_found invoiceNumber gehört nicht zu dieser Bestellung.
409 order_not_invoiced Noch keine Rechnung. Nimm POST /v1/orders/{orderNo}/cancel.
409 invoice_not_archived Die Rechnung wird gerade erzeugt. Versuch es gleich noch einmal.
409 invoice_ambiguous Mehrere Rechnungen, nenne eine in invoiceNumber.
409 refund_via_provider Bezahlt über einen Zahlungsanbieter (oder paymentGateway stripe_transfer/stripe_debit). Erstatte dort.
409 refund_id_conflict Die refundId wurde schon mit anderem Inhalt verwendet.
409 already_fully_corrected Die Rechnung ist schon vollständig storniert.
422 amount_exceeds_remaining Mehr als noch zu korrigieren ist (details.remainingGrossCents).
422 item_not_found / item_quantity_exceeds Position nicht auf der Rechnung, oder mehr Stück als berechnet und noch nicht erstattet.
422 items_amount_mismatch Der Betrag passt nicht zu den Positionen.

Im Testmodus entstehen dieselben Belege mit TEST-S-Nummern. Ein Kunde, der einfach nicht zahlt, ist keine Erstattung: Diese Forderung bucht deine Steuerkanzlei als uneinbringlich aus (§ 17 Abs. 2 Nr. 1 UStG). Melde sie nicht über diesen Endpunkt.

Warum Stornorechnung und nicht Gutschrift

  • Steuerlich heißt „Gutschrift“ nur die Rechnung, die der Leistungsempfänger selbst ausstellt (§ 14 Abs. 2 Satz 5 UStG, Pflichtangabe „Gutschrift“ nach § 14 Abs. 4 Satz 1 Nr. 10 UStG). Eine Stornierung oder Korrektur der eigenen Rechnung ist keine Gutschrift (Abschnitt 14.3 Abs. 2 Satz 5 UStAE). Rechnungskit nennt den Beleg deshalb Stornorechnung.
  • Die Stornorechnung verweist mit Nummer und Datum auf die Originalrechnung, wie es § 31 Abs. 5 Satz 2 UStDV für eine Berichtigung verlangt. Sie hat eine eigene Nummer, die Originalrechnung bleibt unverändert im Archiv.
  • Wer eine E-Rechnung berichtigt, muss das ebenfalls als E-Rechnung tun (Abschnitt 14.11 Abs. 1 Satz 7 UStAE, eingefügt durch BMF-Schreiben vom 15.10.2025, III C 2 - S 7287-a/00019/007/243). Die Stornorechnung entsteht deshalb wie die Rechnung als ZUGFeRD (Rechnungstyp 381).
  • Die Umsatzsteuer wird für den Zeitraum berichtigt, in dem sich das Entgelt ändert (§ 17 Abs. 1 Satz 8 UStG). Bei schon gezahltem Geld zählt die tatsächliche Rückzahlung (Abschnitt 17.1 Abs. 2 Satz 3 UStAE). Schick als refundedAt deshalb bei bezahlten Rechnungen den Tag, an dem du das Geld zurücküberwiesen hast.

Häufige Fragen zur API

Kann die Rechnung den Abrechnungsmonat zeigen, etwa „Oktober 2026“?

Ja, über service_period an der Bestellung (siehe Leistungszeitraum). Die Rechnung zeigt den Zeitraum als Datumsspanne, nicht als Monatsnamen: Aus { "start_date": "2026-10-01", "end_date": "2026-10-31" } wird im Rechnungskopf „Leistungszeitraum: 01.10.2026 – 31.10.2026“, in der ZUGFeRD-XML der Abrechnungszeitraum (BG-14). Mit einem Zeitraum entsteht die Rechnung sofort, ohne auf eine Versandmeldung zu warten. Soll zusätzlich das Wort „Oktober 2026“ auf der Rechnung stehen, schreib es in den Positionsnamen, etwa "name": "Weinabo Oktober 2026".

Wie storniere oder korrigiere ich eine Rechnung über die API?

Das hängt vom Stand ab:

  • Noch keine Rechnung: POST /v1/orders/{orderNo}/cancel. Die Bestellung bekommt nie einen Beleg.
  • Rechnung da, Kauf auf Rechnung oder Vorkasse: POST /v1/orders/{orderNo}/refunds, ganz, mit Teilbetrag oder positionsgenau (siehe Erstattungen melden). Es entsteht eine Stornorechnung mit eigener Nummer.
  • Rechnung da, bezahlt über einen Zahlungsanbieter: Erstatte beim Zahlungsanbieter. Die Stornorechnung entsteht automatisch.

Eine ausgestellte Rechnung ändert oder löscht Rechnungskit nie. Korrigiert wird immer mit einem neuen Beleg. Ist nur die Anschrift desselben Kunden falsch, nimmst du in der App „Rechnungsanschrift korrigieren“ auf der Rechnungsseite (siehe Belege und Archiv), dafür gibt es keinen API-Endpunkt.

Gibt es einen Testmodus ohne echte Rechnung und ohne Mail?

Ja, das Projekt im Testmodus. Dein API-Schlüssel funktioniert dort wie im Echtbetrieb, mit derselben Pipeline samt Prüfung und PDF, aber mit TEST-Nummern (TEST-…, Stornos TEST-S-…). Testbelege gehen nie per E-Mail raus, werden nicht abgerechnet und fließen nicht in DATEV, OSS oder ZM. Andere Kunden-Mails, etwa Zahlungserinnerungen, bekommst du selbst mit „[TEST]“ im Betreff, damit du siehst, was im Echtbetrieb rausginge. Webhooks tragen "test": true. Details unter Testen und Testmodus (Sandbox).

Meldet Rechnungskit, wenn eine Rechnung bezahlt ist?

Ja, auf zwei Wegen:

  • Webhook invoice.paid: kommt, sobald der offene Posten einer Rechnung bei Kauf auf Rechnung oder Vorkasse vollständig bezahlt ist, je Rechnung und Endpunkt genau einmal. Du richtest ihn unter Einstellungen → API bei den Checkout-Webhooks ein, er gilt auch für API-Bestellungen. Payload und Auslöser stehen unter Rechnung bezahlt (invoice.paid).
  • Abfragen: GET /v1/invoices und GET /v1/invoices/{reference} liefern den Block payment mit status (open, partially_paid, paid, uncollectible), paidCents und openCents (siehe Ist die Rechnung bezahlt?).

Als bezahlt gilt die Rechnung, wenn du die Zahlung unter Offene Posten bestätigst, eine zugeordnete Überweisung den Betrag deckt, dein Shop die Bestellung als bezahlt meldet oder ein Guthaben den Rest deckt.

Zustell-Kontrakt (Pflicht für Integratoren)

Dein System muss fehlgeschlagene Pushes wiederholen. Das heißt konkret:

  1. Jeder 5xx- oder Netzwerkfehler ist transient. Auch Deployments verursachen kurze 502-Fenster.
  2. Wiederhole mit exponentiellem Backoff über mindestens 24 Stunden.
  3. Zusätzlich empfohlen: ein täglicher Abgleichs-Job, der alles erneut sendet, was dein System als fehlgeschlagen markiert hat.

Wiederholungen sind immer sicher: Bestellungen sind idempotent über orderNo, der Idempotency-Key-Header dedupliziert parallele Zustellungen.

Typischer Fehlerfall ohne Retry: Die Zahlung kommt an (der Zahlungsanbieter wiederholt automatisch), der Bestell-Push scheitert an einem einzigen 502. Die Zahlung bleibt dann als „Keine Bestellung“ in der Klärung liegen, bis die Bestellung nachgereicht wird.

Grenzen, ehrlich benannt

  • Fehlerantworten folgen den üblichen Codes (400/401/403/404/409/422/429/503), bei 429/503 mit Retry-After. Baue Backoff ein.
  • Anfragen sind über die Request-ID 30 Tage in der API-Aktivität nachvollziehbar.

So findest du es in der App

Rechnungskit ist keine Steuerberatung und keine Rechtsberatung. Dieser Beitrag erklärt allgemeine Zusammenhänge und ersetzt weder die Beratung durch deine Steuerkanzlei (§ 5 StBerG) noch eine Rechtsberatung (§ 3 RDG). Rechnungskit richtet sich an in Deutschland ansässige Unternehmen und bereitet Belege, Steuersätze und Buchungen automatisch auf. Wie dein Einzelfall zu bewerten ist, entscheidest du, im Zweifel gemeinsam mit deiner Steuerkanzlei oder einer Anwaltskanzlei.

de en