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/v1gedacht. Alle anderen Pfade unter/api/gehören zur Web-App, verlangen eine Anmeldung im Browser und antworten auf einen API-Schlüssel immer mit401. 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 /clientsnach ihm suchen oder auf die Antwort409reagieren, 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
POSTden HeaderContent-Type: application/jsonsetzen. - 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 Beispiel2026-11-30. In Antworten: Zeitstempel nach ISO 8601 in UTC, zum Beispiel2026-11-30T00:00:00.000Z. - Antworten können weitere Felder enthalten. Verlässlich sind nur die hier beschriebenen.
| Status | Bedeutung |
|---|---|
200 | Erfolg |
201 | Angelegt |
400 | Eingabe ungültig, Details in error |
401 | Schlüssel fehlt, ist ungültig oder widerrufen, oder die Firma hat keinen Business-Plan |
404 | Nicht gefunden, oder der Eintrag gehört zu einer anderen Firma |
409 | Kunde mit diesem Firmennamen existiert bereits |
429 | Zu viele Anfragen. Der Header Retry-After nennt die Wartezeit in Sekunden |
500 | Fehler 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
| Anfrage | Zweck |
|---|---|
GET /clients | Die neuesten 100 Kunden, ohne gelöschte |
GET /clients/{id} | Ein Kunde |
POST /clients | Kunde anlegen |
GET /offers | Die neuesten 100 Offerten mit Positionen |
GET /offers/{id} | Eine Offerte |
POST /offers | Offerte als Entwurf anlegen |
GET /invoices | Die neuesten 100 Rechnungen mit Positionen |
GET /invoices/{id} | Eine Rechnung |
Kunden
Kunden lesen
GET /clients und GET /clients/{id}
| Feld | Inhalt |
|---|---|
id | ID des Kunden |
companyName | Firmenname |
address, postalCode, city, country | Adresse |
mainContactName, mainContactEmail, mainContactPhone | Hauptkontakt |
industry, website | Branche und Website |
language | Sprache der Dokumente für diesen Kunden: de, en, fr oder it. Leer heisst Sprache der Firma |
notes | Interne Notizen |
createdAt, updatedAt | Angelegt und zuletzt geändert |
_count.invoices, _count.offers | Anzahl 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"
}
| Feld | Pflicht | Hinweis |
|---|---|---|
companyName | ja | höchstens 200 Zeichen |
address | nein | höchstens 300 Zeichen |
postalCode | nein | höchstens 20 Zeichen |
city, country | nein | je höchstens 100 Zeichen |
mainContactName | nein | höchstens 200 Zeichen |
mainContactEmail | nein | gültige E-Mail-Adresse |
mainContactPhone | nein | höchstens 50 Zeichen |
industry | nein | höchstens 100 Zeichen |
website | nein | höchstens 500 Zeichen |
language | nein | de, en, fr oder it |
notes | nein | hö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}
| Feld | Inhalt |
|---|---|
id | ID der Offerte |
offerNumber | Offertnummer, zum Beispiel OF-00042 |
title | Titel |
status | DRAFT Entwurf, SENT versendet, ACCEPTED angenommen, REJECTED abgelehnt, EXPIRED abgelaufen, CONVERTED in eine Rechnung umgewandelt |
offerDate, validUntil | Offertdatum und gültig bis |
amountNet | Betrag nach Rabatt, ohne MWST |
amountGross | Betrag inklusive MWST |
vatRate | MWST-Satz in Prozent |
discount, discountType | Rabatt, PERCENT oder AMOUNT |
currency | CHF oder EUR |
billingType, billingMonths | ONE_TIME einmalig oder MONTHLY monatlich, mit Anzahl Monate |
language | Sprache des Dokuments |
notes | Bemerkungen auf der Offerte |
emailSentAt | Wann die Offerte per E-Mail versendet wurde |
acceptedAt, declinedAt, signedByName | Annahme oder Ablehnung durch den Kunden, Name der unterzeichnenden Person |
clientId, client | Kunde |
items | Positionen, siehe unten |
Jede Position in items:
| Feld | Inhalt |
|---|---|
position | Reihenfolge, ab 1 |
description | Bezeichnung |
details | Beschreibung unter der Bezeichnung |
quantity | Menge |
unit | Einheit, zum Beispiel Stk, Std. oder Pauschale |
unitPrice | Preis pro Einheit, ohne MWST |
totalPrice | Menge 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"
}
| Feld | Pflicht | Hinweis |
|---|---|---|
clientId | ja | ID eines Kunden der eigenen Firma |
title | ja | höchstens 200 Zeichen |
items | ja | 1 bis 200 Positionen |
items[].description | ja | höchstens 500 Zeichen |
items[].unitPrice | ja | Preis pro Einheit ohne MWST |
items[].quantity | nein | ohne Angabe 1 |
items[].unit | nein | ohne Angabe Stk |
items[].details | nein | höchstens 5000 Zeichen |
offerDate | nein | JJJJ-MM-TT, ohne Angabe heute |
validUntil | nein | JJJJ-MM-TT |
vatRate | nein | 8.1, 2.6, 3.8 oder 0. Ohne Angabe 8.1, für Firmen ohne MWST-Pflicht 0 |
discount | nein | nur zusammen mit discountType |
discountType | nein | PERCENT (höchstens 100) oder AMOUNT |
notes | nein | Bemerkungen auf der Offerte |
language | nein | de, en, fr oder it |
currency | nein | CHF oder EUR, ohne Angabe CHF |
billingType | nein | ONE_TIME oder MONTHLY |
billingMonths | nein | 1 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}
| Feld | Inhalt |
|---|---|
id | ID der Rechnung |
invoiceNumber | Rechnungsnummer |
title | Titel |
status | DRAFT Entwurf, OPEN offen, PARTIALLY_PAID teilweise bezahlt, PAID bezahlt, OVERDUE überfällig, CANCELLED storniert |
invoiceDate, dueDate | Rechnungsdatum und fällig am |
paidDate | Bezahlt am |
amountNet | Betrag nach Rabatt, ohne MWST |
amountGross | Betrag inklusive MWST |
vatRate | MWST-Satz in Prozent |
discount, discountType | Rabatt, PERCENT oder AMOUNT |
currency | CHF oder EUR |
qrReference | QR-Referenz des Zahlteils |
language | Sprache des Dokuments |
notes | Bemerkungen auf der Rechnung |
emailSentAt | Wann die Rechnung per E-Mail versendet wurde |
reminderCount | Anzahl versendeter Mahnungen |
cancelledAt, cancelReason | Storno mit Grund |
clientId, client | Kunde |
items | Positionen, 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.