Billify API (v1)

Mit der Billify-API verbinden Business-Kunden Billify mit eigenen Systemen: CRM, Buchhaltung, Automatisierungen oder KI-Agenten. Diese Seite beschreibt alles, was die API heute kann.

Dieselbe Dokumentation gibt es als reinen Text, zum Weitergeben an einen KI-Agenten: https://www.billify.ch/api-dokumentation.md

Kurz für KI-Agenten

  • Basis-URL: https://www.billify.ch/api/v1
  • Jede Anfrage mit dem Header Authorization: Bearer billify_sk_…
  • Für API-Schlüssel sind nur die Pfade unter /api/v1 gedacht. Alle anderen Pfade unter /api/ gehören zur Web-App, verlangen eine Anmeldung im Browser und antworten auf einen API-Schlüssel immer mit 401. Diese Pfade nicht verwenden.
  • Was auf dieser Seite nicht steht, kann die API nicht. Keine Pfade erraten.
  • Beträge, Totale, Nummern und Status setzt Billify selbst. Mitgeschickte Werte dafür werden ignoriert.
  • Vor dem Anlegen eines Kunden mit GET /clients nach ihm suchen oder auf die Antwort 409 reagieren, sonst entstehen Dubletten.

Zugang

  • Die API gehört zum Business-Plan.
  • Schlüssel erstellen: in Billify unter Einstellungen, Team, «API Schlüssel». Das können nur Administratoren der Firma.
  • Der Schlüssel wird nur einmal angezeigt. Billify speichert ihn nicht im Klartext und kann ihn nicht erneut anzeigen. Geht er verloren, einen neuen erstellen.
  • Ein Schlüssel gilt für die ganze Firma. Er funktioniert nur, solange die Person, die ihn erstellt hat, Administrator der Firma ist. Wird sie entfernt oder zum Mitglied gemacht, hört der Schlüssel auf zu funktionieren.
  • Den Schlüssel wie ein Passwort behandeln: nicht in Webseiten-Code, nicht in öffentliche Repositories, nicht in E-Mails. Ein Schlüssel lässt sich in den Einstellungen jederzeit widerrufen.

Anfragen und Antworten

  • Alles in JSON und UTF-8. Bei POST den Header Content-Type: application/json setzen.
  • Erfolg: { "data": … }. Listen enthalten zusätzlich "count".
  • Fehler: { "error": "…" }. Die Meldung nennt bei ungültigen Eingaben das betroffene Feld.
  • IDs sind ganze Zahlen.
  • Beträge sind Dezimalzahlen in der Währung des Dokuments (CHF oder EUR), zum Beispiel 1250.5. Nicht in Rappen.
  • Datum beim Senden: JJJJ-MM-TT, zum Beispiel 2026-11-30. In Antworten: Zeitstempel nach ISO 8601 in UTC, zum Beispiel 2026-11-30T00:00:00.000Z.
  • Antworten können weitere Felder enthalten. Verlässlich sind nur die hier beschriebenen.
StatusBedeutung
200Erfolg
201Angelegt
400Eingabe ungültig, Details in error
401Schlüssel fehlt, ist ungültig oder widerrufen, oder die Firma hat keinen Business-Plan
404Nicht gefunden, oder der Eintrag gehört zu einer anderen Firma
409Kunde mit diesem Firmennamen existiert bereits
429Zu viele Anfragen. Der Header Retry-After nennt die Wartezeit in Sekunden
500Fehler bei Billify. Später nochmals versuchen

Grenzen

  • Höchstens 100 Anfragen pro Minute.
  • Listen liefern die neuesten 100 Einträge, die neuesten zuerst. Blättern und Filtern gibt es noch nicht.

Übersicht

AnfrageZweck
GET /clientsDie neuesten 100 Kunden, ohne gelöschte
GET /clients/{id}Ein Kunde
POST /clientsKunde anlegen
GET /offersDie neuesten 100 Offerten mit Positionen
GET /offers/{id}Eine Offerte
POST /offersOfferte als Entwurf anlegen
GET /invoicesDie neuesten 100 Rechnungen mit Positionen
GET /invoices/{id}Eine Rechnung

Kunden

Kunden lesen

GET /clients und GET /clients/{id}

FeldInhalt
idID des Kunden
companyNameFirmenname
address, postalCode, city, countryAdresse
mainContactName, mainContactEmail, mainContactPhoneHauptkontakt
industry, websiteBranche und Website
languageSprache der Dokumente für diesen Kunden: de, en, fr oder it. Leer heisst Sprache der Firma
notesInterne Notizen
createdAt, updatedAtAngelegt und zuletzt geändert
_count.invoices, _count.offersAnzahl Rechnungen und Offerten

Kunde anlegen

POST /clients

{
  "companyName": "Muster Gartenbau GmbH",
  "address": "Seestrasse 1",
  "postalCode": "8500",
  "city": "Frauenfeld",
  "country": "Schweiz",
  "mainContactName": "Anna Muster",
  "mainContactEmail": "anna@muster-gartenbau.ch",
  "mainContactPhone": "+41 52 000 00 00",
  "language": "de",
  "notes": "Kontakt über Messe"
}
FeldPflichtHinweis
companyNamejahöchstens 200 Zeichen
addressneinhöchstens 300 Zeichen
postalCodeneinhöchstens 20 Zeichen
city, countryneinje höchstens 100 Zeichen
mainContactNameneinhöchstens 200 Zeichen
mainContactEmailneingültige E-Mail-Adresse
mainContactPhoneneinhöchstens 50 Zeichen
industryneinhöchstens 100 Zeichen
websiteneinhöchstens 500 Zeichen
languageneinde, en, fr oder it
notesneinhöchstens 5000 Zeichen

Antwort 201 mit dem neuen Kunden in data.

Gibt es schon einen Kunden mit demselben Firmennamen (Gross- und Kleinschreibung egal), antwortet die API mit 409 und liefert den bestehenden Kunden in data. So entsteht bei einem zweiten Versuch keine Dublette. Wer trotzdem einen zweiten Kunden mit gleichem Namen will: POST /clients?allowDuplicate=true.

Offerten

Offerten lesen

GET /offers und GET /offers/{id}

FeldInhalt
idID der Offerte
offerNumberOffertnummer, zum Beispiel OF-00042
titleTitel
statusDRAFT Entwurf, SENT versendet, ACCEPTED angenommen, REJECTED abgelehnt, EXPIRED abgelaufen, CONVERTED in eine Rechnung umgewandelt
offerDate, validUntilOffertdatum und gültig bis
amountNetBetrag nach Rabatt, ohne MWST
amountGrossBetrag inklusive MWST
vatRateMWST-Satz in Prozent
discount, discountTypeRabatt, PERCENT oder AMOUNT
currencyCHF oder EUR
billingType, billingMonthsONE_TIME einmalig oder MONTHLY monatlich, mit Anzahl Monate
languageSprache des Dokuments
notesBemerkungen auf der Offerte
emailSentAtWann die Offerte per E-Mail versendet wurde
acceptedAt, declinedAt, signedByNameAnnahme oder Ablehnung durch den Kunden, Name der unterzeichnenden Person
clientId, clientKunde
itemsPositionen, siehe unten

Jede Position in items:

FeldInhalt
positionReihenfolge, ab 1
descriptionBezeichnung
detailsBeschreibung unter der Bezeichnung
quantityMenge
unitEinheit, zum Beispiel Stk, Std. oder Pauschale
unitPricePreis pro Einheit, ohne MWST
totalPriceMenge mal Preis, ohne MWST

Offerte anlegen

POST /offers

{
  "clientId": 3,
  "title": "Gartenpflege 2027",
  "offerDate": "2026-10-07",
  "validUntil": "2026-11-30",
  "items": [
    { "description": "Grundpflege Garten", "details": "Rasen, Hecken, Beete", "quantity": 12, "unit": "Monat", "unitPrice": 180 },
    { "description": "Baumschnitt", "quantity": 6.5, "unit": "Std.", "unitPrice": 95 }
  ],
  "vatRate": 8.1,
  "discount": 5,
  "discountType": "PERCENT",
  "notes": "Preise exkl. Entsorgung.",
  "language": "de",
  "currency": "CHF"
}
FeldPflichtHinweis
clientIdjaID eines Kunden der eigenen Firma
titlejahöchstens 200 Zeichen
itemsja1 bis 200 Positionen
items[].descriptionjahöchstens 500 Zeichen
items[].unitPricejaPreis pro Einheit ohne MWST
items[].quantityneinohne Angabe 1
items[].unitneinohne Angabe Stk
items[].detailsneinhöchstens 5000 Zeichen
offerDateneinJJJJ-MM-TT, ohne Angabe heute
validUntilneinJJJJ-MM-TT
vatRatenein8.1, 2.6, 3.8 oder 0. Ohne Angabe 8.1, für Firmen ohne MWST-Pflicht 0
discountneinnur zusammen mit discountType
discountTypeneinPERCENT (höchstens 100) oder AMOUNT
notesneinBemerkungen auf der Offerte
languageneinde, en, fr oder it
currencyneinCHF oder EUR, ohne Angabe CHF
billingTypeneinONE_TIME oder MONTHLY
billingMonthsnein1 bis 120, nur bei MONTHLY
  • Die Offerte entsteht immer als Entwurf. Die Offertnummer vergibt der Nummernkreis der Firma. Versendet wird in Billify, nach einem Blick auf die Vorschau.
  • Positionsbeträge und Totale rechnet Billify selbst, gleich wie im Formular und im PDF.

Antwort 201 mit der Offerte samt Positionen. Zusätzlich enthält sie appUrl, den Link zur Offerte in Billify.

Rechnungen

Rechnungen lesen

GET /invoices und GET /invoices/{id}

FeldInhalt
idID der Rechnung
invoiceNumberRechnungsnummer
titleTitel
statusDRAFT Entwurf, OPEN offen, PARTIALLY_PAID teilweise bezahlt, PAID bezahlt, OVERDUE überfällig, CANCELLED storniert
invoiceDate, dueDateRechnungsdatum und fällig am
paidDateBezahlt am
amountNetBetrag nach Rabatt, ohne MWST
amountGrossBetrag inklusive MWST
vatRateMWST-Satz in Prozent
discount, discountTypeRabatt, PERCENT oder AMOUNT
currencyCHF oder EUR
qrReferenceQR-Referenz des Zahlteils
languageSprache des Dokuments
notesBemerkungen auf der Rechnung
emailSentAtWann die Rechnung per E-Mail versendet wurde
reminderCountAnzahl versendeter Mahnungen
cancelledAt, cancelReasonStorno mit Grund
clientId, clientKunde
itemsPositionen, gleich aufgebaut wie bei den Offerten

Beispiele

Den Schlüssel in einer Umgebungsvariable halten, nicht im Befehl selbst:

export BILLIFY_API_KEY="billify_sk_…"

Kunden lesen:

curl https://www.billify.ch/api/v1/clients \
  -H "Authorization: Bearer $BILLIFY_API_KEY"

Kunden anlegen:

curl -X POST https://www.billify.ch/api/v1/clients \
  -H "Authorization: Bearer $BILLIFY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"companyName": "Muster Gartenbau GmbH", "city": "Frauenfeld", "mainContactEmail": "anna@muster-gartenbau.ch"}'

Offerte anlegen:

curl -X POST https://www.billify.ch/api/v1/offers \
  -H "Authorization: Bearer $BILLIFY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"clientId": 3, "title": "Gartenpflege 2027", "items": [{"description": "Grundpflege Garten", "quantity": 12, "unit": "Monat", "unitPrice": 180}]}'

Eine Rechnung lesen:

curl https://www.billify.ch/api/v1/invoices/128 \
  -H "Authorization: Bearer $BILLIFY_API_KEY"

Was die API noch nicht kann

  • Rechnungen anlegen
  • PDFs von Rechnungen oder Offerten abrufen
  • Rechnungen oder Offerten versenden
  • Kunden, Offerten oder Rechnungen ändern oder löschen

Das alles geht in der Web-App. Wer eine dieser Funktionen über die API braucht, meldet sich unter info@billify.ch.

Nachvollziehbarkeit

Jeder über die API angelegte Kunde und jede Offerte steht im Audit-Log der Firma, mit dem Namen des verwendeten Schlüssels.

Stabilität

Pfade und Felder unter /api/v1 bleiben bestehen. Neue Felder und neue Pfade können dazukommen. Änderungen, die bestehende Anbindungen brechen würden, kommen nur unter einer neuen Version.

Änderungen

  • Oktober 2026: Kunden und Offerten über die API anlegen (POST /clients, POST /offers).
  • Davor: nur lesen.