Invoices API
Rechnungen (Invoices) ermöglichen es Ihnen, professionelle Rechnungen zu erstellen, verwalten und als PDF zu generieren. Rechnungen können mit Kunden, Projekten, Zeiterfassungen und Ausgaben verknüpft werden.
Invoice-Objekt
{
"id": 21274,
"user_id": 456,
"company_id": 123,
"customer_id": 410,
"contact_id": 175,
"project_id": 89,
"proposal_id": null,
"bank_account_id": 847,
"tax_rate_id": 12,
"income_account_id": 923,
"entry_id": null,
"receivable_entry_id": null,
"number": "260001",
"title": "Webentwicklung Januar 2024",
"lang": "de",
"date": "2024-01-15",
"payable_until": "2024-01-29",
"reminder_due_date": null,
"status": "sent",
"positions": [
{
"id": 1,
"type": "position",
"description": "Frontend-Entwicklung",
"amount": 10,
"price": 150.00,
"discount_amount": 0,
"discount_type": null,
"sum": 1500.00,
"unit": "h",
"order": 0
}
],
"remarks_top": null,
"remarks": "Zahlbar innert 14 Tagen.",
"currency": "CHF",
"total_value": 1500.00,
"discount_rate": null,
"discount_amount": 0,
"total_value_with_discount": 1500.00,
"vat_active": true,
"vat_included": false,
"vat_amount": 121.50,
"total_value_with_vat": 1621.50,
"final_value": 1621.50,
"reference": "000000000000000000000212740",
"qr_reference": "000000000000000000000212740",
"group_by": null,
"times_order": "asc",
"document_settings": {},
"exchange_rate_times": 1,
"exchange_rate_expenses": 1,
"repeat": false,
"repeat_interval": null,
"repeat_stop_at": null,
"repeat_notification_to": null,
"add_rounding_difference": false,
"rounding_difference": 0,
"without_debitor_on_qr": false,
"created_at": "2024-01-15T10:30:00.000000Z",
"updated_at": "2024-01-15T14:22:00.000000Z",
"isReminder": false,
"formatted": {
"date": "15.01.2024",
"payable_until": "29.01.2024"
},
"payableDays": 14,
"type": "invoice",
"overdue": false,
"payable_until_formatted": "29.01.2024",
"final_value_formatted": "1'621.50",
"is_shared": false,
"share_token": null
}Attribute
| Feld | Typ | Beschreibung |
|---|---|---|
id | integer | Eindeutige Rechnungs-ID |
user_id | integer | ID des Benutzers, der die Rechnung erstellt hat |
company_id | integer | ID der zugehörigen Firma |
customer_id | integer | ID des Kunden |
contact_id | integer|null | ID des Kontakts beim Kunden |
project_id | integer|null | ID des verknüpften Projekts |
proposal_id | integer|null | ID der verknüpften Offerte |
bank_account_id | integer|null | ID des Bankkontos für QR-Rechnung |
tax_rate_id | integer|null | ID des MwSt-Satzes |
income_account_id | integer|null | ID des Ertragskontos |
entry_id | integer|null | ID des Buchungseintrags (wenn bezahlt) |
receivable_entry_id | integer|null | ID der Debitorbuchung (bei vereinbarter MwSt-Methode) |
number | string | Rechnungsnummer |
title | string|null | Titel der Rechnung |
lang | string | Sprache (de, en, fr, it) |
date | date | Rechnungsdatum |
payable_until | date | Zahlungsfrist |
reminder_due_date | date|null | Mahnfrist |
status | string | Status (siehe Status-Werte) |
positions | array | Rechnungspositionen |
remarks_top | string|null | Bemerkungen oben |
remarks | string|null | Bemerkungen unten |
currency | string | Währung (CHF, EUR, USD, GBP) |
total_value | decimal | Zwischensumme (ohne Rabatt/MwSt) |
discount_rate | decimal|null | Rabatt in Prozent |
discount_amount | decimal | Rabattbetrag |
total_value_with_discount | decimal | Summe nach Rabatt |
vat_active | boolean | MwSt aktiviert |
vat_included | boolean | MwSt inklusiv |
vat_amount | decimal | MwSt-Betrag |
total_value_with_vat | decimal | Summe mit MwSt |
final_value | decimal | Endbetrag |
reference | string|null | QR-Referenznummer |
repeat | boolean | Wiederkehrende Rechnung |
repeat_interval | string|null | Intervall (monthly, quarterly, yearly) |
overdue | boolean | Überfällig |
is_shared | boolean | Öffentlicher Link aktiv |
share_token | string|null | Token für öffentlichen Link |
Status-Werte
| Status | Beschreibung |
|---|---|
open | Entwurf/Offen |
sent | Versendet |
paid | Bezahlt |
reminder_one | 1. Mahnung |
reminder_two | 2. Mahnung |
reminder_three | 3. Mahnung |
Positions-Objekt
{
"id": 1,
"type": "position",
"description": "Frontend-Entwicklung",
"amount": 10,
"price": 150.00,
"discount_amount": 0,
"discount_type": null,
"sum": 1500.00,
"unit": "h",
"order": 0,
"date": null,
"parent_id": null,
"formatted": {
"sum": "1'500.00",
"date": null
}
}| Feld | Typ | Beschreibung |
|---|---|---|
type | string | Typ (position, text, collection) |
description | string|null | Beschreibung |
title | string|null | Titel (nur bei collection) |
amount | decimal | Menge |
price | decimal | Einzelpreis |
discount_amount | decimal | Rabatt |
discount_type | string|null | Rabatttyp (absolute, percentage) |
sum | decimal | Summe |
unit | string|null | Einheit (z.B. h, Stk.) |
order | integer | Reihenfolge |
parent_id | integer|null | ID der übergeordneten Collection |
Endpoints
Alle Rechnungen auflisten
GET /companies/{company_id}/invoicesRuft eine Liste aller Rechnungen der Firma ab.
Parameter
| Parameter | Typ | Standard | Beschreibung |
|---|---|---|---|
filter[id] | integer | - | Filtern nach spezifischer ID |
filter[customer_id] | integer | - | Filtern nach Kunde |
filter[project_id] | integer | - | Filtern nach Projekt |
filter[proposal_id] | integer | - | Filtern nach Offerte |
filter[entry_id] | integer | - | Filtern nach verknüpfter Buchung |
filter[user_id] | integer | - | Filtern nach Benutzer |
filter[bank_account_id] | integer | - | Filtern nach Bankkonto |
filter[tax_rate_id] | integer | - | Filtern nach MwSt-Satz |
filter[repeat] | boolean | - | Filtern nach wiederkehrend |
filter[number] | string | - | Teilsuche nach Rechnungsnummer |
filter[date] | string | - | Filtern nach Datum (Bereich, z.B. 2024-01-01,2024-12-31) |
filter[status] | string|array | - | Filtern nach Status (einzeln oder als Array) |
Filter-Optionen für Status
| Wert | Beschreibung |
|---|---|
open | Offene Rechnungen |
sent | Versendete Rechnungen |
paid | Bezahlte Rechnungen |
unpaid | Alle nicht bezahlten Rechnungen |
overdue | Überfällige Rechnungen |
reminder | Alle Mahnungen |
all | Alle Rechnungen |
Response
{
"data": [
{
"id": 21274,
"number": "260001",
"title": "Webentwicklung Januar 2024",
"date": "2024-01-15",
"status": "sent",
"final_value": 1621.50,
"customer": {
"id": 410,
"name": "Muster AG"
}
}
],
"meta": {
"count": 1,
"total": 45,
"sum": 1621.50,
"sum_formatted": "1'621.50"
}
}Beispiel
curl -H "Authorization: Bearer 1|abcdef123456789..." \
"https://app.milkee.ch/api/v2/companies/123/invoices"
# Mit Filtern
curl -H "Authorization: Bearer 1|abcdef123456789..." \
"https://app.milkee.ch/api/v2/companies/123/invoices?filter[status]=unpaid&filter[customer_id]=410"Neue Rechnung erstellen
POST /companies/{company_id}/invoicesErstellt eine neue Rechnung.
Request Body
{
"customer_id": 410,
"date": "2024-01-15",
"payable_until": "2024-01-29",
"positions": "[{\"description\":\"Frontend-Entwicklung\",\"amount\":10,\"price\":150}]",
"title": "Webentwicklung Januar 2024",
"remarks": "Zahlbar innert 14 Tagen.",
"tax_rate_id": 12,
"lang": "de"
}Wichtige Felder
| Feld | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
customer_id | integer | Ja | Kunde für die Rechnung |
positions | string (JSON) | Ja | Positionen als JSON-String |
date | date | Nein | Rechnungsdatum (Standard: heute) |
payable_until | date | Nein | Zahlungsfrist (Standard: + Zahlungsfrist der Firma) |
number | string | Nein | Rechnungsnummer (automatisch wenn nicht angegeben) |
title | string | Nein | Titel |
lang | string | Nein | Sprache (de, en, fr, it) |
remarks | string | Nein | Bemerkungen unten |
remarks_top | string | Nein | Bemerkungen oben |
discount_rate | decimal | Nein | Gesamtrabatt in Prozent (0-100) |
tax_rate_id | integer | Nein | MwSt-Satz ID |
project_id | integer | Nein | Verknüpftes Projekt |
proposal_id | integer | Nein | Verknüpfte Offerte |
contact_id | integer | Nein | Kontakt beim Kunden |
bank_account_id | integer | Nein | Bankkonto für QR-Rechnung |
income_account_id | integer | Nein | Ertragskonto für Buchung |
currency | string | Nein | Währung |
time_ids | array | Nein | IDs der zu verknüpfenden Zeiteinträge |
entry_ids | array | Nein | IDs der zu verknüpfenden Ausgaben |
Positionen-Format
[
{
"type": "position",
"description": "Entwicklung",
"amount": 10,
"price": 150,
"unit": "h"
},
{
"type": "text",
"text": "Zusätzliche Hinweise"
},
{
"type": "collection",
"title": "Paket A",
"positions": [
{
"description": "Unterposition 1",
"amount": 1,
"price": 100
}
]
}
]Wiederkehrende Rechnungen
Business-Abo erforderlich
Wiederkehrende Rechnungen sind nur mit dem Business-Abo verfügbar. Bei fehlendem Abo wird ein 402 Payment Required Fehler zurückgegeben.
{
"customer_id": 410,
"positions": "[{\"description\":\"Monatliche Wartung\",\"amount\":1,\"price\":500}]",
"repeat": true,
"repeat_interval": "monthly",
"repeat_stop_at": "2025-12-31",
"repeat_notification_to": "buchhaltung@example.com"
}Response
HTTP/1.1 201 Created{
"id": 21275,
"number": "260002",
"status": "open",
"final_value": 1621.50,
"created_at": "2024-01-15T14:30:00.000000Z"
}Beispiel
curl -X POST \
-H "Authorization: Bearer 1|abcdef123456789..." \
-H "Content-Type: application/json" \
-d '{
"customer_id": 410,
"positions": "[{\"description\":\"Entwicklung\",\"amount\":10,\"price\":150}]",
"tax_rate_id": 12
}' \
"https://app.milkee.ch/api/v2/companies/123/invoices"Einzelne Rechnung abrufen
GET /companies/{company_id}/invoices/{invoice_id}Ruft die Details einer spezifischen Rechnung ab.
Response
Die Response enthält das vollständige Invoice-Objekt mit allen geladenen Beziehungen:
customer- Kundeninformationencontact- Kontaktpersonproject- Verknüpftes Projektproposal- Verknüpfte Offertebank_account- Bankkontotax_rate- MwSt-Satzattachments- Anhängeentries- Verknüpfte Einnahmen
Beispiel
curl -H "Authorization: Bearer 1|abcdef123456789..." \
"https://app.milkee.ch/api/v2/companies/123/invoices/21274"Rechnung aktualisieren
PUT /companies/{company_id}/invoices/{invoice_id}Aktualisiert eine bestehende Rechnung.
Bearbeitungssperre
Rechnungen können nur im Status open bearbeitet werden. Bei allen anderen Status wird ein 400 Bad Request Fehler zurückgegeben.
Request Body
{
"title": "Webentwicklung Januar 2024 (aktualisiert)",
"positions": "[{\"description\":\"Angepasste Position\",\"amount\":12,\"price\":150}]",
"discount_rate": 5
}Aktualisierbare Felder
number,title,langdate,payable_until,reminder_due_datepositions,remarks,remarks_topdiscount_rate,currencycustomer_id,contact_id,project_idbank_account_id,tax_rate_id,income_account_idtime_ids,entry_idsrepeat,repeat_intervalwithout_debitor_on_qrexchange_rate_times,exchange_rate_expenses
Beispiel
curl -X PUT \
-H "Authorization: Bearer 1|abcdef123456789..." \
-H "Content-Type: application/json" \
-d '{"title": "Aktualisierter Titel", "discount_rate": 10}' \
"https://app.milkee.ch/api/v2/companies/123/invoices/21274"Rechnung löschen
DELETE /companies/{company_id}/invoices/{invoice_id}Löscht eine Rechnung unwiderruflich.
Auswirkungen
Beim Löschen werden verknüpfte Zeiteinträge wieder auf Status "open" gesetzt.
Response
HTTP/1.1 204 No ContentStatus ändern (Mark As)
GET /companies/{company_id}/invoices/{invoice_id}/mark-asÄndert den Status einer Rechnung. Die Parameter werden als Query-Parameter übergeben.
Erlaubte Status-Übergänge
open → sent
sent → open, paid, reminder_one
paid → unpaid
reminder_one → undo_reminder_one, reminder_two, paid
reminder_two → reminder_one, reminder_three, paid
reminder_three → reminder_two, paidAls versendet markieren
?status=sentVereinbarte MwSt-Methode
Wenn die vereinbarte MwSt-Methode (vat_accrual_method_active) für die Firma aktiviert ist, wird beim Setzen auf sent automatisch eine Debitorbuchung (Forderungsbuchung) erstellt. Diese wird im Feld receivable_entry_id referenziert.
Als bezahlt markieren
?status=paid&createEntry=true&paidDate=2024-01-20&bank_account_id=847| Parameter | Typ | Beschreibung |
|---|---|---|
status | string | Zielstatus (paid) |
createEntry | boolean | Buchungseintrag erstellen |
paidDate | date | Zahlungsdatum |
bank_account_id | integer | Bankkonto für Buchung |
sum | decimal | Zahlungsbetrag (optional) |
currency | string | Währung (optional) |
exchange_rate | decimal | Wechselkurs (optional) |
tax_rate_id | integer | MwSt-Satz für Buchung (optional) |
Zurück auf offen setzen
?status=openAchtung
Wenn eine Debitorbuchung existiert (receivable_entry_id), wird diese beim Zurücksetzen auf open gelöscht.
Als unbezahlt markieren
?status=unpaidAchtung
Bei Status "unpaid" wird der verknüpfte Buchungseintrag gelöscht.
Mahnung setzen
?status=reminder_one&reminder_due_date=2024-02-15Beispiel
# Als versendet markieren
curl -H "Authorization: Bearer 1|abcdef123456789..." \
"https://app.milkee.ch/api/v2/companies/123/invoices/21274/mark-as?status=sent"
# Als bezahlt markieren mit Buchung
curl -H "Authorization: Bearer 1|abcdef123456789..." \
"https://app.milkee.ch/api/v2/companies/123/invoices/21274/mark-as?status=paid&createEntry=true&paidDate=2024-01-20&bank_account_id=847"PDF herunterladen
GET /companies/{company_id}/invoices/{invoice_id}/pdfGeneriert und liefert die Rechnung als PDF.
Response
{
"base64": "data:application/pdf;base64,JVBERi0xLjQK...",
"fileName": "Rechnung_260001.pdf"
}Beispiel
curl -H "Authorization: Bearer 1|abcdef123456789..." \
"https://app.milkee.ch/api/v2/companies/123/invoices/21274/pdf"Nächste Rechnungsnummer abrufen
GET /companies/{company_id}/invoices/numberGibt die nächste freie Rechnungsnummer zurück.
Response
"260003"Batch-Operationen
Mehrere Rechnungen aktualisieren
PATCH /companies/{company_id}/invoices/batch{
"ids": [21274, 21275, 21276],
"updateData": {
"discount_rate": 5
}
}Mehrere Rechnungen löschen
DELETE /companies/{company_id}/invoices/batch{
"ids": [21274, 21275]
}Mehrere Status ändern
POST /companies/{company_id}/invoices/batch/mark-as{
"ids": [21274, 21275],
"updateData": {
"status": "sent"
}
}Fehlertoleranz
Bei ungültigen Status-Übergängen werden betroffene Rechnungen übersprungen, andere werden verarbeitet.
Rechnungspositionen API
Positionen können auch einzeln über dedizierte Endpoints verwaltet werden.
Positionen auflisten
GET /companies/{company_id}/invoices/{invoice_id}/positionsPosition erstellen
POST /companies/{company_id}/invoices/{invoice_id}/positions{
"type": "position",
"description": "Neue Position",
"amount": 5,
"price": 100,
"unit": "h"
}Position aktualisieren
PUT /companies/{company_id}/invoices/{invoice_id}/positions/{position_id}Position löschen
DELETE /companies/{company_id}/invoices/{invoice_id}/positions/{position_id}Aktivitäten
GET /companies/{company_id}/invoices/{invoice_id}/activitiesGibt die Aktivitätshistorie einer Rechnung zurück.
{
"2024-01-15 10:30:00": {
"date": "15.01.2024",
"message": "Rechnung wurde erstellt."
},
"2024-01-15 14:22:00": {
"date": "15.01.2024",
"message": "Status geändert auf Versendet."
}
}Fehlerbehandlung
400 Bad Request
{
"message": "Rechnung kann nicht gelöscht werden, da sie bereits bezahlt ist."
}{
"message": "Rechnungen können nur im Status \"Offen\" bearbeitet werden."
}402 Payment Required
{
"message": "Wiederkehrende Rechnungen sind nur mit dem Business-Abo möglich"
}422 Validation Error
{
"message": "The given data was invalid.",
"errors": {
"customer_id": ["Kunde ist erforderlich."],
"positions": ["Positionen müssen ein gültiges JSON sein."]
}
}QR-Code Fehler
{
"message": "Deine IBAN ist nicht valid: CH1234567890. Deine Daten für den QR-Code sind nicht valid."
}Best Practices
Rechnung mit Zeiterfassung erstellen
// 1. Offene Zeiten eines Projekts abrufen
const times = await api.getProjectTimes(projectId, { status: 'open' });
// 2. Rechnung mit Zeiteinträgen erstellen
const invoice = await api.createInvoice({
customer_id: customerId,
project_id: projectId,
time_ids: times.map(t => t.id),
positions: JSON.stringify(times.map(t => ({
description: t.description,
amount: t.hours + (t.minutes / 60),
price: t.hourly_rate,
unit: 'h'
})))
});Status-Workflow
async function processInvoice(companyId, invoiceId) {
// Rechnung versenden (GET mit Query-Parametern)
await api.get(`/companies/${companyId}/invoices/${invoiceId}/mark-as`, {
params: { status: 'sent' }
});
// Nach Zahlungseingang
await api.get(`/companies/${companyId}/invoices/${invoiceId}/mark-as`, {
params: {
status: 'paid',
createEntry: true,
paidDate: new Date().toISOString().split('T')[0]
}
});
}Fehlerbehandlung für QR-Rechnungen
async function downloadInvoicePDF(companyId, invoiceId) {
try {
const { base64, fileName } = await api.get(
`/companies/${companyId}/invoices/${invoiceId}/pdf`
);
return { base64, fileName };
} catch (error) {
if (error.status === 422 && error.message.includes('QR')) {
// IBAN oder Adressdaten prüfen
console.error('QR-Code Fehler:', error.message);
}
throw error;
}
}Nächste Schritte
- Customers API - Kunden verwalten
- Proposals API - Offerten erstellen
- Projects API - Projekte verwalten
- Webhooks einrichten
