Zurück zur Anleitung

Rechnungen per API: vom Kunden zur versandten Rechnung

Der ganze Fakturierungsweg in REST-Aufrufen: Kunde, Vorlage, Rechnung, Finalisierung, Versand, PDF, dann Offerten und wiederkehrende Rechnungen.

Entwickler7 Min. Lesezeit
Inhaltsverzeichnis+

Diese Anleitung macht in curl nach, was der Fakturierungsbildschirm mit der Maus tut. Jeder Aufruf nutzt denselben Schlüssel und dieselbe Basis-URL wie Erste Schritte mit der API.

BASE=https://<deployment>.convex.site/api/v1
KEY=ba_...

1. Voraussetzungen

Nehmen Sie aus GET /me die id einer Firma, bei der modules.invoicing aktiv ist, und halten Sie sie bereit:

COMPANY=<companyId>

2. Den Kunden anlegen

curl -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{ "name": "Muster AG", "email": "billing@muster.example",
        "address": { "kind": "swiss", "country": "CH", "street": "Rue du Lac",
                     "streetNumber": "12", "postalCode": "1700", "city": "Fribourg" } }' \
  $BASE/companies/$COMPANY/clients

Die Antwort 201 enthält den Kunden mit seiner id. Ein Kunde gehört zu einer Firma: Den Kunden einer anderen Firma auf einer Rechnung zu verwenden, antwortet 403 ACCESS_DENIED. Das Kundenkontingent des Plans gilt (403 CLIENT_LIMIT_REACHED).

3. Eine Vorlage wählen

curl -H "Authorization: Bearer $KEY" $BASE/companies/$COMPANY/invoice-templates

Die Vorlagenliste ist nicht paginiert und nimmt keinen Parameter. Die Vorlage mit isDefault: true ist die, auf die eine Rechnung zurückfällt, wenn Sie nichts angeben: templateId ist beim Erstellen optional. Das Logo wird in der App verwaltet, hasLogo ist schreibgeschützt.

4. Die Rechnung erstellen

curl -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{ "clientId": "<clientId>", "lang": "fr", "currency": "CHF",
        "issueDate": "2026-03-01", "dueDate": "2026-03-31",
        "items": [{ "description": "Design", "quantity": 2, "unitPrice": 150, "vatRate": 8.1 }] }' \
  $BASE/companies/$COMPANY/invoices

clientId, lang, issueDate, dueDate und currency sind Pflicht; templateId, discount, shippingCosts, notes, paymentTerms, showQrBill und items sind optional. Die Nummer kommt vom Zähler der Firma, und die Rechnung startet als Entwurf (draft).

Das Erstellen zählt zum monatlichen Rechnungskontingent (403 INVOICE_LIMIT_REACHED), und eine andere Währung als CHF braucht einen bezahlten Plan (403 CURRENCY_PRO_ONLY).

5. Finalisieren und versenden

curl -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{ "event": "FINALIZE" }' $BASE/companies/$COMPANY/invoices/<invoiceId>/transition

curl -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{ "kind": "invoice" }' $BASE/companies/$COMPANY/invoices/<invoiceId>/send

Der Lebenszyklus ist der der App: draftready (FINALIZE)sentpaid (MARK_PAID, mit optionalem paidDate). CANCEL storniert aus draft, ready, sent oder overdue; REOPEN holt eine stornierte Rechnung in den Entwurf zurück. Jedes andere Paar antwortet 409 INVALID_TRANSITION, mit dem abgelehnten Paar in message.

Der Versand ist kein Übergang, sondern POST …/send: kind: "invoice" nur aus ready (sonst 409 BILL_EMAIL_INVALID_STATUS), kind: "reminder" nur aus overdue. Das PDF wird angehängt und die Mail geht in der Rechnungssprache raus; ein Kunde ohne E-Mail-Adresse antwortet 422 CLIENT_EMAIL_MISSING.

6. Das PDF herunterladen

curl -L -H "Authorization: Bearer $KEY" \
  $BASE/companies/$COMPANY/invoices/<invoiceId>/pdf -o invoice.pdf

curl -L -H "Authorization: Bearer $KEY" \
  $BASE/companies/$COMPANY/invoices/<invoiceId>/qr-bill-pdf -o qr-bill.pdf

Beide Routen antworten 302 auf eine kurzlebige URL (15 Minuten): Folgen Sie der Weiterleitung immer mit -L und cachen Sie das Location nie. Die Dateien werden bei Bedarf erzeugt, der erste Aufruf nach einer Änderung rendert das Dokument. 503 PDF_UNAVAILABLE heisst, das Rendern ist fehlgeschlagen: erneut versuchen.

7. Eine ausgestellte Rechnung ändern

Hat eine Rechnung draft verlassen, ändert sie sich nur mit Begründung: Jede Kopf- oder Positionsänderung muss einen nicht leeren modificationReason tragen, der im Protokoll der Rechnung festgehalten wird. Ohne ihn lautet die Antwort 422 MODIFICATION_REASON_REQUIRED, und nichts wird geschrieben.

curl -X PATCH -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{ "dueDate": "2026-04-30", "modificationReason": "Client asked for a longer term" }' \
  $BASE/companies/$COMPANY/invoices/<invoiceId>

Positionen haben eigene Routen (POST …/items, PATCH …/items/{itemId}, DELETE …/items/{itemId}), alle geben die ganze Rechnung mit neu berechneten Summen zurück. Da ein DELETE keinen Body hat, reist die Begründung als Query-Parameter:

curl -X DELETE -H "Authorization: Bearer $KEY" \
  "$BASE/companies/$COMPANY/invoices/<invoiceId>/items/<itemId>?modificationReason=Line%20billed%20twice"

8. Offerten und wiederkehrende Rechnungen

Offerten (/companies/{companyId}/quotes) haben die Form von Rechnungen, mit einer zusätzlichen Regel: Nur ein Entwurf lässt sich ändern. Eine versandte Offerte antwortet 409 QUOTE_NOT_EDITABLE; setzen Sie sie mit REVERT_TO_DRAFT in den Entwurf zurück oder duplizieren Sie sie. Offerten sind auf jedem Plan unbegrenzt, nur die Umwandlung verbraucht eine Rechnung aus dem Kontingent:

curl -X POST -H "Authorization: Bearer $KEY" $BASE/companies/$COMPANY/quotes/<quoteId>/convert

Die Umwandlung erstellt eine Rechnung im Entwurf und antwortet 201 mit invoiceId und invoiceNumber. Eine Offerte wird einmal umgewandelt (409 QUOTE_ALREADY_CONVERTED), eine abgelehnte oder abgelaufene gar nicht (409 QUOTE_NOT_CONVERTIBLE).

Eine wiederkehrende Rechnung (/companies/{companyId}/recurring-invoices) ist kein Dokument, sondern Kopf, Positionen und Uhr: startDate setzt den nächsten Lauf, endDate oder maxOccurrences beenden ihn, und mit autoSend wird jede erzeugte Rechnung finalisiert und an den Kunden versandt. Es gibt keine Route pro Position, PUT …/items ersetzt den ganzen Satz:

curl -X PUT -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{ "items": [
        { "description": "Abonnement", "quantity": 1, "unitPrice": 180, "vatRate": 8.1 },
        { "description": "Support", "quantity": 2, "unitPrice": 90, "vatRate": 8.1 }
      ] }' \
  $BASE/companies/$COMPANY/recurring-invoices/<scheduleId>/items

Für Lohn und Buchhaltung geht es weiter mit Lohn, Ausgaben und Buchhaltung per API; zu Paginierung, Daten und Fehlercodes lesen Sie Konventionen und Fehler. Jedes Feld ist in der Swagger UI Ihres Deployments beschrieben (…/api/docs).