Rechnungen in Ihre Buchhaltung
Buchen Sie die Rechnungen aus Klantly automatisch in Ihrer Buchhaltungssoftware, mit dem PDF dazu, und melden Sie Zahlungen von der Bank zurück.
Sie erstellen und versenden Rechnungen in Klantly; Ihre Buchhaltung läuft woanders. Mit der API buchen Sie jede versendete Rechnung in Ihrer Buchhaltungssoftware, legen das PDF zur Buchung ab und erfassen Zahlungen, die auf der Bank eingehen, an der Rechnung in Klantly.
Was Sie brauchen
Einen API-Schlüssel mit diesen Scopes:
| Scope | Wofür |
|---|---|
invoices.read |
Rechnungen und ihr PDF abrufen. |
webhooks.manage |
Optional: einen Webhook für die Rechnungs-Events anlegen. |
invoices.write |
Optional: Zahlungen von der Bank zurückmelden oder ein angenommenes Angebot in eine Rechnung umwandeln. |
invoices.send |
Optional: den Zahlungslink abrufen, damit der Kunde online bezahlen kann. |
Die Funktion Rechnungen muss für Ihr Unternehmen aktiv sein.
Schritt 1: wissen, wann gebucht wird
Buchen Sie eine Rechnung erst, wenn sie versendet wurde. Ein Entwurf (draft) kann sich noch ändern oder verschwinden; eine versendete Rechnung steht fest.
Hören Sie mit einem Webhook auf diese Events:
| Event | Was Sie tun |
|---|---|
invoice.sent |
Die Rechnung als Ausgangsrechnung buchen. Kommt auch beim erneuten Versand (einer Erinnerung): buchen Sie nach id, nicht doppelt. |
invoice.paid |
Die Rechnung ist vollständig bezahlt. Markieren Sie die Buchung als ausgeglichen. |
invoice.cancelled |
Die Rechnung wurde storniert. Legen Sie in Ihrer Buchhaltung eine Gutschrift an. |
Rufen Sie lieber selbst ab, zum Beispiel stündlich? Dann fragen Sie die Rechnungen ab, die sich seit Ihrem letzten Durchlauf geändert haben:
curl --globoff "https://app.klantly.com/api/v1/invoices?filter[updated_since]=2026-10-06T00:00:00Z&sort=updated_at&limit=100" \
-H "Authorization: Bearer $KLANTLY_API_KEY"Blättern Sie mit next_cursor weiter, bis er null ist, siehe Paginierung. Merken Sie sich den Zeitpunkt, zu dem Ihr Durchlauf begann, abzüglich einer Minute Puffer, und verwenden Sie ihn beim nächsten Mal als filter[updated_since]. Überspringen Sie Entwürfe und prüfen Sie bei jeder Rechnung status.
Schritt 2: die Rechnung buchen
Alles, was Sie für die Buchung brauchen, steht in der Rechnung:
| Feld | Inhalt |
|---|---|
number |
Die Rechnungsnummer, zum Beispiel FAC-2026-00042. Verwenden Sie sie als Referenz der Buchung. |
invoice_date, due_date |
Rechnungsdatum und Fälligkeitsdatum (JJJJ-MM-TT). |
customer |
Name, Firmenname, Adresse, USt-IdNr. und Handelsregisternummer, wie sie auf der Rechnung stehen. |
items |
Die Positionen, mit line_total (ohne MwSt.), tax_rate und is_taxable. |
discount_amount |
Ein Rabatt auf die ganze Rechnung, als Betrag. |
subtotal, tax_amount, total |
Die Summen. Es gilt immer: subtotal − discount_amount + tax_amount = total. |
is_term_invoice, term_percentage |
Eine Abschlagsrechnung: ein Teil eines Angebots. |
Beträge sind Text mit zwei Nachkommastellen, zum Beispiel "1305.79". Rechnen Sie damit als Dezimalzahl, nicht als Gleitkommazahl, sonst entstehen Rundungsdifferenzen.
Buchen Sie je Steuersatz die Summe von line_total der Positionen mit diesem Satz. Hat die Rechnung einen discount_amount, verteilen Sie ihn anteilig auf die Steuersätze oder buchen Sie ihn als eigene Position. Prüfen Sie danach, dass Ihre Buchung total ergibt.
Hinweis
Eine Position mit unit_price_incl wurde als Bruttopreis eingegeben. line_total_incl ist dann der Betrag, den der Kunde für diese Position zahlt, auf den Cent genau. Für die Buchung verwenden Sie weiterhin line_total und tax_rate.
Schritt 3: das PDF zur Buchung ablegen
Rufen Sie die Rechnung so ab, wie der Kunde sie erhalten hat:
curl "https://app.klantly.com/api/v1/invoices/7a1d9e42-5c3b-4f6a-9d8e-2c4b6a8d0e13/pdf" \
-H "Authorization: Bearer $KLANTLY_API_KEY" \
-o FAC-2026-00042.pdfDie Antwort ist die Datei selbst. Beim ersten Mal erstellt Klantly das PDF, das kann einige Sekunden dauern; danach kommt es aus dem Cache. Diese Anfrage zählt zum Limit für aufwendige Aktionen, rufen Sie das PDF also einmal ab und speichern Sie es.
Schritt 4: Zahlungen zurückmelden
Geht eine Zahlung auf der Bank ein, melden Sie sie an Klantly. Ihr Team sieht dann, dass die Rechnung bezahlt ist, und Klantly versendet keine Zahlungserinnerungen mehr:
curl -X POST "https://app.klantly.com/api/v1/invoices/7a1d9e42-5c3b-4f6a-9d8e-2c4b6a8d0e13/payments" \
-H "Authorization: Bearer $KLANTLY_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: bank-20261006-000318" \
-d '{
"amount": "1943.00",
"payment_method": "bank_transfer",
"reference": "DE89370400440532013000 / FAC-2026-00042"
}'- Setzen Sie die Transaktionsnummer der Bank in den
Idempotency-Key: So wird eine Zahlung nie doppelt erfasst, auch wenn Sie die Anfrage wiederholen. - Eine Teilzahlung ist möglich. Die Rechnung steht dann auf
partial, bis der ganze Betrag eingegangen ist; danach aufpaid, und Sie erhalteninvoice.paid. - Der Betrag kann nie höher sein als
amount_due. Ist er höher, erhalten Sie422füramount.
Erfassen Sie Zahlungen nur in Ihrer Buchhaltung, überspringen Sie diesen Schritt.
Den Kunden online bezahlen lassen
Hat Ihr Unternehmen Mollie verbunden und die Funktion Online-Zahlungen, bezahlt der Kunde eine versendete Rechnung auf einer Zahlungsseite. Diesen Link rufen Sie ab, um ihn selbst zu teilen, zum Beispiel in einer Erinnerung aus Ihrer Buchhaltung oder per WhatsApp:
curl "https://app.klantly.com/api/v1/invoices/7a1d9e42-5c3b-4f6a-9d8e-2c4b6a8d0e13/payment-link" \
-H "Authorization: Bearer $KLANTLY_API_KEY"Die Antwort enthält url und amount_due. Hat die Rechnung einen Zahlungsplan, wählt der Kunde auf dieser Seite die nächste Rate. Eine Zahlung über diese Seite wird automatisch an der Rechnung erfasst: Sie erhalten invoice.paid, sobald alles eingegangen ist, und müssen sie nicht mit Schritt 4 zurückmelden.
Warnung
Wer den Link hat, kann die Rechnung ansehen und bezahlen. Teilen Sie ihn nur mit dem Kunden. Deshalb erfordert er den Scope invoices.send.
Ist die Rechnung noch ein Entwurf, bereits bezahlt oder storniert, erhalten Sie 409 mit invalid_state_transition; ist Mollie noch nicht verbunden, 409 mit conflict.
Vom Angebot zur Rechnung
Hat der Kunde ein Angebot angenommen, machen Sie daraus in einem Schritt einen Rechnungsentwurf. Kunde, Positionen, Rabatt und Texte werden übernommen:
curl -X POST "https://app.klantly.com/api/v1/quotes/3b8e5f20-6a1c-4d9e-8b7a-5c4d3e2f1a09/invoice" \
-H "Authorization: Bearer $KLANTLY_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: angebot-OF-26457-rechnung" \
-d '{"payment_term_days": 14}'Ein Angebot hat höchstens eine Rechnung. Fragen Sie erneut, erhalten Sie 409 mit dem Code conflict und der ID der vorhandenen Rechnung in invoice_id. Versenden Sie sie danach mit POST /invoices/{id}/send.
Alles zusammen
use GuzzleHttp\Client;
/**
* Bucht die Rechnungen, die sich seit $since geändert haben, und gibt den Zeitpunkt für den nächsten Durchlauf zurück.
* $book erhält die Rechnung und eine Funktion, die das PDF abruft: Rufen Sie sie nur auf, wenn die Rechnung
* noch nicht gebucht ist. $markPaid und $credit aktualisieren Ihre Buchhaltung.
*/
function syncInvoices(Client $klantly, string $since, callable $book, callable $markPaid, callable $credit): string
{
$startedAt = gmdate('Y-m-d\TH:i:s\Z', time() - 60); // Eine Minute Puffer für Uhrzeitabweichungen.
$cursor = null;
do {
$page = json_decode((string) $klantly->get('invoices', [
'query' => array_filter([
'filter' => ['updated_since' => $since],
'sort' => 'updated_at',
'limit' => 100,
'cursor' => $cursor,
]),
])->getBody(), true);
foreach ($page['data'] as $invoice) {
match ($invoice['status']) {
'draft' => null, // Noch nicht buchen.
'cancelled' => $credit($invoice),
default => $book($invoice, fn () => downloadPdf($klantly, $invoice)), // Nach $invoice['id'] deduplizieren.
};
if ($invoice['status'] === 'paid') {
$markPaid($invoice);
}
}
$cursor = $page['meta']['next_cursor'];
} while ($cursor !== null);
return $startedAt;
}
function downloadPdf(Client $klantly, array $invoice): string
{
$path = sys_get_temp_dir() . '/' . $invoice['number'] . '.pdf';
$klantly->get("invoices/{$invoice['id']}/pdf", ['sink' => $path]);
return $path;
}import { writeFile } from 'node:fs/promises';
const BASE_URL = 'https://app.klantly.com/api/v1';
const headers = { Authorization: `Bearer ${process.env.KLANTLY_API_KEY}` };
async function klantly(path) {
const response = await fetch(`${BASE_URL}/${path}`, { headers });
const json = await response.json();
if (!response.ok) throw new Error(json.detail ?? json.title);
return json;
}
async function downloadPdf(invoice) {
const response = await fetch(`${BASE_URL}/invoices/${invoice.id}/pdf`, { headers });
if (!response.ok) throw new Error(`PDF von ${invoice.number} nicht abgerufen (${response.status})`);
const path = `/tmp/${invoice.number}.pdf`;
await writeFile(path, Buffer.from(await response.arrayBuffer()));
return path;
}
// Bucht die Rechnungen, die sich seit `since` geändert haben, und gibt den Zeitpunkt für den nächsten Durchlauf zurück.
export async function syncInvoices(since, { book, markPaid, credit }) {
const startedAt = new Date(Date.now() - 60_000).toISOString(); // Eine Minute Puffer für Uhrzeitabweichungen.
let cursor = null;
do {
const params = new URLSearchParams({ 'filter[updated_since]': since, sort: 'updated_at', limit: '100' });
if (cursor) params.set('cursor', cursor);
const page = await klantly(`invoices?${params}`);
for (const invoice of page.data) {
if (invoice.status === 'draft') continue; // Noch nicht buchen.
if (invoice.status === 'cancelled') {
await credit(invoice);
continue;
}
// book erhält eine Funktion, die das PDF abruft: nur aufrufen, wenn die Rechnung noch nicht gebucht ist.
await book(invoice, () => downloadPdf(invoice)); // Nach invoice.id deduplizieren.
if (invoice.status === 'paid') await markPaid(invoice);
}
cursor = page.meta.next_cursor;
} while (cursor);
return startedAt;
}Fehler abfangen
404mitnot_found: Die Rechnung existiert nicht (mehr), zum Beispiel ein gelöschter Entwurf.409mitinvalid_state_transition: Sie erfassen eine Zahlung an einer stornierten Rechnung oder wandeln ein Angebot um, das nicht angenommen wurde.422mitvalidation_failed: zum Beispiel eine Zahlung überamount_due. Inerrorssteht je Feld, was falsch ist.429mitrate_limited: Warten Sie die Sekunden ausRetry-Afterab und versuchen Sie es erneut. Siehe Rate Limits.
Zuletzt aktualisiert am 17. September 2026