Questa guida percorre un mese di paghe completo: preparare il mese, creare le buste paga, convalidarle, inviarle, correggerle se serve e infine recuperare i PDF. La creazione dei dipendenti e il dettaglio dei campi di una busta paga sono in Paghe, spese e contabilità con l'API. Stessa chiave e stesso URL di base di Iniziare con l'API:
BASE=https://<deployment>.convex.site/api/v1
KEY=ba_...
COMPANY=<companyId>1. Gli stati della busta paga
Tutte queste rotte richiedono il piano Business e il modulo paghe (modules.payroll in GET /me). Il campo status di una busta paga determina il resto di questa guida:
draft: modificabile ed eliminabile. Viene ricalcolata automaticamente se riguarda il mese in corso o un mese successivo, quando cambiano i dati del dipendente.validated: bloccata, i ricalcoli automatici non la toccano più. Una busta paga vi arriva convalidateo con il suo invio. Una correzione la riporta indraft.declared: immutabile, la convalida, la correzione e l'eliminazione rispondono409 PAYSLIP_DECLARED. Nessuna rotta dell'API imposta questo stato.
2. Preparare il mese
Queste rotte richiedono solo la lettura e non scrivono nulla. Il calendario riporta i giorni festivi infrasettimanali di ogni mese, per il cantone indicato oppure, senza workCanton, per quello della società:
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 ha un elemento per mese (l'indice 0 è gennaio) e resta vuoto quando hasCalendar vale false. holiday-variants elenca i regimi di un cantone, ed è vuoto se il cantone ne ha uno solo; la società sceglie il proprio con publicHolidayVariant in PATCH /companies/{companyId}. Per un dipendente con salario orario, l'anteprima mostra ciò che la busta paga pagherà:
curl -H "Authorization: Bearer $KEY" \
"$BASE/companies/$COMPANY/employees/<employeeId>/holiday-preview?year=2026&month=8&canton=FR"source vale calendar (con days, hours e la lista holidays), percentage (con rate) oppure none. Per un dipendente con salario mensile, data vale null. Prima di assumere o di cambiare un contratto, l'anteprima legale verifica condizioni non ancora registrate:
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-previewLa risposta è una lista di avvisi (code, level, params), ad esempio vacationBelowStatutoryMinimum o cantonalMinimumWageBelow. Non impedisce nulla: sta a te tenerne conto. Infine, le assenze del mese:
curl -H "Authorization: Bearer $KEY" \
"$BASE/companies/$COMPANY/employees/<employeeId>/absence-lines?year=2026&month=7"lines propone le righe da riportare sulla busta paga, con label, amount, type e gli indicatori subjectToCharges, subjectToIS e subjectToInsurance. Non viene scritto nulla: ricopia in variableItems quelle che vuoi mantenere. Se missingParams vale true, non è caricato alcun parametro d'indennità, quindi una lista vuota non significa che non ci sia nulla da pagare. Due altri controlli completano il quadro: GET …/employees/{employeeId}/absence-compliance?year=2026&month=7 e GET …/employees/{employeeId}/avs-gap?year=2026.
3. Creare le buste paga del mese
curl -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{ "payslips": [
{ "employeeId": "<id1>", "year": 2026, "month": 7, "canton": "FR",
"variableItems": [{ "label": "Premio di risultato", "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"Il lotto non è atomico: la risposta riporta il risultato di ogni riga (status vale created oppure error, con un code). Una riga di variableItems richiede label, amount e type (overtime, bonus, commission, custom, absence, deduction). Le buste paga partono in draft; rileggile, con le loro righe, tramite GET …/payslips/{payslipId}.
4. Convalidare
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": "Ore da rivedere" }' $BASE/companies/$COMPANY/payslips/<payslipId>/unvalidatevalidate blocca la busta paga senza inviarla. Il suo corpo è obbligatorio, anche se vuoto, e reason vi è facoltativo. La risposta riporta changed: false se la busta paga era già convalidata. unvalidate la riporta in draft, con un reason obbligatorio, registrato nello storico. Nessuna delle due ricalcola la busta paga: una busta paga tornata in bozza viene ricalcolata solo alla prossima modifica sul dipendente o sulla società.
5. Inviare
curl -X POST -H "Authorization: Bearer $KEY" $BASE/companies/$COMPANY/payslips/<payslipId>/sendSenza corpo, risposta 204. Il PDF parte per e-mail all'indirizzo del dipendente, nella lingua della sua busta paga, e una bozza passa a validated; sentStatus e sentDate vengono valorizzati. Una busta paga già inviata può essere inviata di nuovo. Da prevedere: 422 EMPLOYEE_EMAIL_MISSING, 503 PDF_UNAVAILABLE e 502 EMAIL_SEND_FAILED, questi ultimi due da riprovare. Nella sandbox non viene consegnato nulla, ma lo stato cambia allo stesso modo.
6. Correggere
Inizia misurando l'effetto di una correzione. preview-update richiede solo la lettura e non scrive nulla:
curl -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{ "canton": "FR", "variableItems": [{ "label": "Premio di risultato", "amount": 800, "type": "bonus" }] }' \
$BASE/companies/$COMPANY/payslips/<payslipId>/preview-updateLa risposta riporta currentNetSalary, newNetSalary e netDelta, oppure null se il calcolo non va a buon fine, ad esempio in assenza di una tariffa dell'imposta alla fonte. La correzione vera e propria accetta gli stessi campi, più un motivo e il dettaglio delle modifiche:
curl -X PATCH -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{ "canton": "FR",
"variableItems": [{ "label": "Premio di risultato", "amount": 800, "type": "bonus" }],
"reason": "Premio corretto",
"changedFields": [{ "field": "Premio di risultato", "previousValue": "500", "newValue": "800" }],
"createCorrection": true, "correctionLabel": "Correzione premio di giugno" }' \
$BASE/companies/$COMPANY/payslips/<payslipId>- Risposta
204.canton,reasonechangedFieldssono obbligatori; unreasonvuoto risponde422 REASON_REQUIRED. Rinvia ilcantonletto sulla busta paga. - Senza
variableItemsooverrides, i valori salvati restano. Un array li sostituisce,[]li cancella.overridesaccetta coppie{ key, value }, dovekeyè la chiave di una riga della busta paga, ad esempioaanp. Le righe di correzione provenienti da un mese precedente vengono sempre mantenute. - Una busta paga convalidata torna in
draft, e il suo PDF e il suo indicatore di invio vengono azzerati: richiamavalidateosend. changedFieldsnon viene calcolato: è la tua descrizione della modifica, copiata nello storico.
Lo storico si legge, dal più recente al più vecchio, con reason, changedFields e createdAt:
curl -H "Authorization: Bearer $KEY" $BASE/companies/$COMPANY/payslips/<payslipId>/modificationsSe a cambiare è stata un'assenza, GET …/employees/{employeeId}/payslips-to-update?startDate=2026-03-01&endDate=2026-03-31 elenca le buste paga emesse le cui righe di assenza non corrispondono più (proposedCount a 0: la riga deve semplicemente sparire). Questa rotta non scrive nulla, la correzione resta da fare.
Per ripartire da zero, DELETE …/payslips/{payslipId} elimina una busta paga, con le sue righe e il suo storico. POST …/payslips/bulk-delete accetta da 1 a 100 ids ed è atomico: un identificativo sconosciuto o di un'altra società risponde 404 NOT_FOUND e non viene eliminato nulla. La risposta riporta affected e skipped, che conta gli identificativi ripetuti.
7. Duplicare su un altro mese
curl -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{ "year": 2026, "month": 8 }' $BASE/companies/$COMPANY/payslips/<payslipId>/duplicateLa risposta 200 riporta l'id della copia, un draft dello stesso dipendente, ricalcolato per il suo mese con le righe variabili dell'originale. PDF, stato di invio e convalida non vengono ripresi. Da prevedere: 409 PAYSLIP_ALREADY_EXISTS se quel mese esiste già, 422 PAYSLIP_PERIOD_OUTSIDE_EMPLOYMENT fuori dal periodo di impiego.
8. Recuperare i PDF in lotto
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 va da 1 a 25 (10 per impostazione predefinita) e questa rotta richiede l'accesso in scrittura, perché genera i file mancanti. Ripeti la chiamata con lo stesso periodo e il nextCursor ricevuto finché non vale null. Una riga in errore non ferma il lotto: rilanciala da sola con GET …/payslips/{payslipId}/pdf. Gli url hanno vita breve (15 minuti), scaricali subito.
9. Monitorare l'area dipendente
curl -H "Authorization: Bearer $KEY" $BASE/companies/$COMPANY/employee-portal-links
curl -H "Authorization: Bearer $KEY" $BASE/companies/$COMPANY/employees/<employeeId>/portal-linkLa lista è paginata e riporta, per dipendente, employeeId e status. status contiene lastSentAt, expiresAt e active, oppure vale null se non è stato inviato alcun link. Queste rotte si limitano a leggere e non restituiscono mai il link stesso: l'invio si fa nell'app, come spiega Gestire l'area dipendente.
10. Riprese, LAINF e AVS: cosa risponde oggi
In produzione ogni mese si calcola da solo: il cumulo annuale del massimale dell'assicurazione contro la disoccupazione (AD), i periodi datati dell'assicurazione contro gli infortuni (LAINF) e la scelta AVS annuale non entrano nel calcolo delle buste paga. Le rotte corrispondenti figurano comunque nel contratto e rispondono così:
GET …/payslips/{payslipId}/cumulative-statusrisponde{ "enabled": false, "unqualifiedMonths": [] }.validateesendnon si bloccano quindi per un mese precedente mancante.…/employees/{employeeId}/year-openings/{year}legge (nullsenza ripresa), salva (PUT, conthroughMonth,acGrosseacBase) ed elimina una ripresa dei cumuli. Non modifica nessuna busta paga ordinaria: la leggono solo i pagamenti legati a un anno di acquisizione, versati dopo l'uscita del dipendente o acquisiti prima dell'età di riferimento.…/employees/{employeeId}/laa-periodsrestituisce[]finché non è registrato alcun periodo, e…/laa-periods/simulatecalcola senza scrivere nulla.…/laa-summary/{year}resta vuoto in questo caso.…/employees/{employeeId}/avs-choices/{year}legge e salva una scelta annuale, senza effetto sul calcolo. Richiede un numero AVS valido (422 PAYROLL_AVS_IDENTITY_REQUIRED) e un anno a partire dal 2023 (422 PAYROLL_AVS_CHOICE_INVALID). La rinuncia alla franchigia che conta oggi è il campoavsFranchiseWaiveddel dipendente.
Per la paginazione, i corpi delle richieste e i codici di errore, continua con Convenzioni ed errori. Il dettaglio di ogni campo è nella Swagger UI del tuo deployment (…/api/docs).