Diese Anleitung geht einen kompletten Lohnmonat durch: den Monat vorbereiten, die Lohnabrechnungen erstellen, validieren, senden, bei Bedarf korrigieren und schliesslich die PDFs abrufen. Das Anlegen der Mitarbeitenden und die Einzelheiten der Felder einer Lohnabrechnung stehen in Lohn, Ausgaben und Buchhaltung per API. Derselbe Schlüssel und dieselbe Basis-URL wie in Erste Schritte mit der API:
BASE=https://<deployment>.convex.site/api/v1
KEY=ba_...
COMPANY=<companyId>1. Die Status der Lohnabrechnung
Alle diese Routen setzen den Plan Business und das Lohnmodul voraus (modules.payroll in GET /me). Das Feld status einer Lohnabrechnung steuert den Rest dieser Anleitung:
draft: änderbar und löschbar. Sie wird automatisch neu berechnet, wenn sie den laufenden oder einen späteren Monat betrifft und sich die Daten des Mitarbeitenden ändern.validated: eingefroren, die automatischen Neuberechnungen fassen sie nicht mehr an. Eine Lohnabrechnung erreicht diesen Status durchvalidateoder durch ihren Versand. Eine Korrektur setzt sie wieder aufdraft.declared: unveränderlich, Validierung, Korrektur und Löschen antworten409 PAYSLIP_DECLARED. Keine Route der API setzt diesen Status.
2. Den Monat vorbereiten
Diese Routen brauchen nur Leserechte und schreiben nichts. Der Kalender liefert die Feiertage, die in jedem Monat auf einen Wochentag fallen, für den angegebenen Kanton oder, ohne workCanton, für den Kanton der Firma:
curl -H "Authorization: Bearer $KEY" "$BASE/companies/$COMPANY/payroll/calendar?year=2026"
curl -H "Authorization: Bearer $KEY" "$BASE/companies/$COMPANY/payroll/holiday-variants?year=2026&canton=FR"weekdayHolidaysByMonth hat ein Element pro Monat (Index 0 ist Januar) und bleibt leer, wenn hasCalendar den Wert false hat. holiday-variants listet die Feiertagsregelungen eines Kantons auf, leer, wenn der Kanton nur eine hat; die Firma wählt ihre mit publicHolidayVariant in PATCH /companies/{companyId}. Für Mitarbeitende im Stundenlohn zeigt die Vorschau, was die Lohnabrechnung auszahlen wird:
curl -H "Authorization: Bearer $KEY" \
"$BASE/companies/$COMPANY/employees/<employeeId>/holiday-preview?year=2026&month=8&canton=FR"source ist calendar (mit days, hours und der Liste holidays), percentage (mit rate) oder none. Für Mitarbeitende im Monatslohn ist data gleich null. Bevor Sie jemanden anstellen oder einen Vertrag ändern, prüft die gesetzliche Vorschau Bedingungen, die noch nicht gespeichert sind:
curl -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{ "year": 2026,
"employee": { "salaryMode": "monthly", "grossMonthlySalary": 3200, "workPercentage": 100,
"weeklyHours": 42, "vacationWeeks": 4, "dateOfBirth": "2003-04-12",
"thirteenthSalaryEnabled": false, "workCanton": "GE" } }' \
$BASE/companies/$COMPANY/payroll/statutory-previewDie Antwort ist eine Liste von Warnungen (code, level, params), zum Beispiel vacationBelowStatutoryMinimum oder cantonalMinimumWageBelow. Sie verhindert nichts, Sie müssen sie selbst berücksichtigen. Zuletzt die Absenzen des Monats:
curl -H "Authorization: Bearer $KEY" \
"$BASE/companies/$COMPANY/employees/<employeeId>/absence-lines?year=2026&month=7"lines schlägt die Zeilen vor, die auf die Lohnabrechnung gehören, mit label, amount, type und den Kennzeichen subjectToCharges, subjectToIS und subjectToInsurance. Es wird nichts geschrieben: Übernehmen Sie die Zeilen, die Sie behalten, in variableItems. Hat missingParams den Wert true, ist kein Entschädigungsparameter geladen, und eine leere Liste heisst daher nicht, dass nichts auszuzahlen wäre. Zwei weitere Kontrollen ergänzen das: GET …/employees/{employeeId}/absence-compliance?year=2026&month=7 und GET …/employees/{employeeId}/avs-gap?year=2026.
3. Die Lohnabrechnungen des Monats erstellen
curl -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{ "payslips": [
{ "employeeId": "<id1>", "year": 2026, "month": 7, "canton": "FR",
"variableItems": [{ "label": "Erfolgsprämie", "amount": 500, "type": "bonus" }] },
{ "employeeId": "<id2>", "year": 2026, "month": 7, "canton": "FR", "hoursWorked": 120 }
] }' $BASE/companies/$COMPANY/payslips/batch
curl -H "Authorization: Bearer $KEY" "$BASE/companies/$COMPANY/payslips?year=2026&month=7"Der Stapel ist nicht atomar: Die Antwort liefert das Ergebnis jeder Zeile (status ist created oder error, mit einem code). Eine Zeile in variableItems verlangt label, amount und type (overtime, bonus, commission, custom, absence, deduction). Die Lohnabrechnungen starten als draft; lesen Sie sie mit ihren Zeilen über GET …/payslips/{payslipId} erneut aus.
4. Validieren
curl -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{}' $BASE/companies/$COMPANY/payslips/<payslipId>/validate
curl -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{ "reason": "Stunden prüfen" }' $BASE/companies/$COMPANY/payslips/<payslipId>/unvalidatevalidate friert die Lohnabrechnung ein, ohne sie zu senden. Der Body ist Pflicht, auch wenn er leer ist, und reason ist darin optional. Die Antwort liefert changed: false, wenn die Lohnabrechnung schon validiert war. unvalidate setzt sie auf draft zurück, mit einem Pflichtfeld reason, das in der Änderungshistorie festgehalten wird. Keine der beiden Routen berechnet die Lohnabrechnung neu: Eine wieder zum Entwurf gewordene Lohnabrechnung wird erst bei der nächsten Änderung am Mitarbeitenden oder an der Firma neu berechnet.
5. Senden
curl -X POST -H "Authorization: Bearer $KEY" $BASE/companies/$COMPANY/payslips/<payslipId>/sendOhne Body, Antwort 204. Das PDF geht per E-Mail an die Adresse des Mitarbeitenden, in der Sprache der Lohnabrechnung, und ein Entwurf wechselt auf validated; sentStatus und sentDate werden gesetzt. Eine bereits versandte Lohnabrechnung lässt sich erneut senden. Zu erwarten: 422 EMPLOYEE_EMAIL_MISSING, 503 PDF_UNAVAILABLE und 502 EMAIL_SEND_FAILED, die letzten beiden erneut versuchen. In der Sandbox wird nichts zugestellt, aber der Status ändert sich genauso.
6. Korrigieren
Messen Sie zuerst die Wirkung einer Korrektur. preview-update braucht nur Leserechte und schreibt nichts:
curl -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{ "canton": "FR", "variableItems": [{ "label": "Erfolgsprämie", "amount": 800, "type": "bonus" }] }' \
$BASE/companies/$COMPANY/payslips/<payslipId>/preview-updateDie Antwort enthält currentNetSalary, newNetSalary und netDelta, oder null, wenn die Berechnung nicht zustande kommt, zum Beispiel ohne Quellensteuertarif. Die Korrektur selbst nimmt dieselben Felder, dazu einen Grund und die Einzelheiten der Änderungen:
curl -X PATCH -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{ "canton": "FR",
"variableItems": [{ "label": "Erfolgsprämie", "amount": 800, "type": "bonus" }],
"reason": "Prämie korrigiert",
"changedFields": [{ "field": "Erfolgsprämie", "previousValue": "500", "newValue": "800" }],
"createCorrection": true, "correctionLabel": "Korrektur Prämie Juni" }' \
$BASE/companies/$COMPANY/payslips/<payslipId>- Antwort
204.canton,reasonundchangedFieldssind Pflicht; ein leeres Feldreasonantwortet422 REASON_REQUIRED. Senden Sie dencantonzurück, den Sie auf der Lohnabrechnung gelesen haben. - Ohne
variableItemsoderoverridesbleiben die gespeicherten Werte erhalten. Ein Array ersetzt sie,[]löscht sie.overridesnimmt Paare{ key, value }entgegen, wobeikeyder Schlüssel einer Zeile der Lohnabrechnung ist, zum Beispielaanp. Korrekturzeilen aus einem früheren Monat bleiben immer erhalten. - Eine validierte Lohnabrechnung geht zurück auf
draft, und ihr PDF sowie ihr Versandkennzeichen werden zurückgesetzt: Rufen Sievalidateodersenderneut auf. changedFieldswird nicht berechnet: Es ist Ihre Beschreibung der Änderung, die in die Änderungshistorie übernommen wird.
Die Änderungshistorie wird von der neuesten zur ältesten Änderung gelesen, mit reason, changedFields und createdAt:
curl -H "Authorization: Bearer $KEY" $BASE/companies/$COMPANY/payslips/<payslipId>/modificationsHat sich eine Absenz geändert, listet GET …/employees/{employeeId}/payslips-to-update?startDate=2026-03-01&endDate=2026-03-31 die ausgestellten Lohnabrechnungen auf, deren Absenzzeilen nicht mehr stimmen (proposedCount gleich 0: Die Zeile muss einfach verschwinden). Diese Route schreibt nichts, die Korrektur müssen Sie noch selbst vornehmen.
Um bei null zu beginnen, löscht DELETE …/payslips/{payslipId} eine Lohnabrechnung samt Zeilen und Änderungshistorie. POST …/payslips/bulk-delete nimmt 1 bis 100 ids entgegen und ist atomar: Eine unbekannte Kennung oder die Kennung einer anderen Firma antwortet 404 NOT_FOUND, und nichts wird gelöscht. Die Antwort liefert affected und skipped; Letzteres zählt die wiederholten Kennungen.
7. Auf einen anderen Monat duplizieren
curl -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{ "year": 2026, "month": 8 }' $BASE/companies/$COMPANY/payslips/<payslipId>/duplicateDie Antwort 200 liefert die id der Kopie, einen draft desselben Mitarbeitenden, für seinen Monat neu berechnet und mit den variablen Zeilen des Originals. PDF, Versandstatus und Validierung werden nicht übernommen. Zu erwarten: 409 PAYSLIP_ALREADY_EXISTS, wenn dieser Monat schon existiert, 422 PAYSLIP_PERIOD_OUTSIDE_EMPLOYMENT ausserhalb der Anstellungsperiode.
8. PDFs im Stapel abrufen
curl -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{ "year": 2026, "month": 7, "limit": 25 }' $BASE/companies/$COMPANY/payslips/pdf-batch
curl -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{ "year": 2026, "month": 7, "limit": 25, "cursor": "<nextCursor>" }' \
$BASE/companies/$COMPANY/payslips/pdf-batch{
"data": [
{ "payslipId": "...", "url": "https://...", "error": null },
{ "payslipId": "...", "url": null, "error": "PDF_UNAVAILABLE" }
],
"nextCursor": "..."
}limit reicht von 1 bis 25 (standardmässig 10), und diese Route verlangt Schreibrechte, weil sie die fehlenden Dateien erzeugt. Wiederholen Sie den Aufruf mit demselben Zeitraum und dem erhaltenen nextCursor, bis dieser null ist. Eine fehlgeschlagene Zeile stoppt den Stapel nicht: Rufen Sie sie einzeln mit GET …/payslips/{payslipId}/pdf erneut ab. Die urls sind kurzlebig (15 Minuten), laden Sie sie sofort herunter.
9. Den Mitarbeiterbereich nachverfolgen
curl -H "Authorization: Bearer $KEY" $BASE/companies/$COMPANY/employee-portal-links
curl -H "Authorization: Bearer $KEY" $BASE/companies/$COMPANY/employees/<employeeId>/portal-linkDie Liste ist paginiert und liefert je Person employeeId und status. status enthält lastSentAt, expiresAt und active oder ist null, wenn noch kein Link gesendet wurde. Diese Routen lesen nur und liefern nie den Link selbst: Der Versand erfolgt in der App, wie Mitarbeiterbereich verwalten erklärt.
10. Übernahmen, UVG und AHV: was heute antwortet
In der Produktion wird jeder Monat für sich berechnet: Die Jahreskumulation des Höchstbetrags der Arbeitslosenversicherung (ALV), die datierten Perioden der Unfallversicherung (UVG) und die jährliche AHV-Wahl fliessen nicht in die Berechnung der Lohnabrechnungen ein. Die entsprechenden Routen stehen trotzdem im Vertrag, und sie antworten:
GET …/payslips/{payslipId}/cumulative-statusantwortet{ "enabled": false, "unqualifiedMonths": [] }.validateundsendblockieren daher nicht wegen eines fehlenden früheren Monats.…/employees/{employeeId}/year-openings/{year}liest (nullohne Übernahme), speichert (PUT, mitthroughMonth,acGrossundacBase) und löscht eine Übernahme der Kumulationen. Sie ändert keine gewöhnliche Lohnabrechnung: Gelesen wird sie nur von Zahlungen, die einem Erwerbsjahr zugeordnet sind und nach dem Austritt des Mitarbeitenden ausgerichtet oder vor dem Referenzalter erworben wurden.…/employees/{employeeId}/laa-periodsliefert[], solange keine Periode gespeichert ist, und…/laa-periods/simulaterechnet, ohne etwas zu schreiben.…/laa-summary/{year}bleibt in diesem Fall leer.…/employees/{employeeId}/avs-choices/{year}liest und speichert eine jährliche Wahl, ohne Wirkung auf die Berechnung. Sie verlangt eine gültige AHV-Nummer (422 PAYROLL_AVS_IDENTITY_REQUIRED) und ein Jahr ab 2023 (422 PAYROLL_AVS_CHOICE_INVALID). Der Verzicht auf den Freibetrag, der heute zählt, ist das FeldavsFranchiseWaiveddes Mitarbeitenden.
Zu Paginierung, Bodies und Fehlercodes lesen Sie weiter in Konventionen und Fehler. Jedes Feld ist in der Swagger UI Ihres Deployments beschrieben (…/api/docs).