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.
- Nimmt die Bestellung anidempotent über deine orderNo
- Ordnet die Zahlung zuvom angebundenen Zahlungsanbieter, paymentRef beschleunigt das
- Erstellt den Belegmit EN-16931-Validierung und PDF
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}.
Abo-Link per API: eigene Referenz und vorausgefüllte E-Mail
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": "…" } }
planist 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 (etwamigrated_from), lehnt die API ab.returnUrl: Nach dem erfolgreichen Kauf leitet die Danke-Seite dorthin weiter, mit?subscription=<Abo-Id>am Ende (dieselbe Id wiesubscriptionIdim Webhook). OhnereturnUrlgilt 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 zureturnUrl. - Dein bestehender API-Schlüssel mit
orders:writereicht. EinenIdempotency-Keyberü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.
Portal-Link per API: Kündigung und Widerruf aus deinem Kundenbereich
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 diesubscriptionIdaus den subscription.*-Webhooks und der?subscription=-Parameter deinerreturnUrl.- 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.canceledundsubscription.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" }
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.
- Deine AnwendungBestellung mit stripe_transferplus E-Mail-Adresse
- RechnungskitRechnung und ZahlungsauftragLeistung sofort, Ware mit der Versandmeldung
- Dein KundeÜberweistan die virtuelle Stripe-IBAN, mit Stripes Referenz als Verwendungszweck
- StripeOrdnet zuRechnungskit schließt den offenen Posten
So läuft es ab:
- Bei einer Leistung entsteht die Rechnung sofort, bei Ware erst mit der Versandmeldung über /v1/fulfillments.
- Gleichzeitig legt Rechnungskit in deinem Stripe-Konto einen Zahlungsauftrag für Banküberweisung an.
- Auf der Rechnung stehen die virtuelle Stripe-IBAN deines Kunden und, als Verwendungszweck, Stripes Referenz für genau diese Zahlung.
- 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.
- 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. - 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.statusistopen. - 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 alspaymentRef. Das geht auch später, indem du die Bestellung noch einmal sendest. - 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.
| 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.
| 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
refundedAtdeshalb 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/invoicesundGET /v1/invoices/{reference}liefern den Blockpaymentmitstatus(open,partially_paid,paid,uncollectible),paidCentsundopenCents(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:
- Jeder 5xx- oder Netzwerkfehler ist transient. Auch Deployments verursachen kurze 502-Fenster.
- Wiederhole mit exponentiellem Backoff über mindestens 24 Stunden.
- 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
Zuletzt geändert
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.