Torna alla guida

API Bill Alps: convenzioni ed errori

Ciò che vale su tutte le rotte: busta, paginazione, date, corpi stretti, errori, utilizzo, e cosa fa un reset della sandbox alle tue chiavi.

Sviluppatori9 min di lettura
Indice+

Le altre guide seguono dei percorsi; questa raccoglie le regole che valgono per ogni chiamata, da tenere a portata di mano mentre scrivi il tuo client.

BASE=https://<deployment>.convex.site/api/v1
KEY=ba_...

1. La busta data

Ogni risposta riuscita avvolge il suo contenuto in data:

{ "data": [ ... ] }

Due eccezioni senza corpo: 204 No Content sulle eliminazioni e su alcune azioni (PATCH …/payslips/{payslipId}, POST …/payslips/{payslipId}/send, POST …/quotes/{quoteId}/send, POST …/invoices/{invoiceId}/cancel-payment), e 302 sui download di PDF, giustificativi e loghi, da seguire con -L.

2. Paginazione

Le liste delle risorse di società sono paginate, dalla più recente alla più vecchia (i dipendenti seguono l'ordine di creazione):

{ "data": [ ... ], "nextCursor": "..." }

Alcune liste non sono paginate e rispondono con un array semplice sotto data: GET /companies, GET /mandates, GET /companies/{companyId}/invoice-templates, GET …/payroll/holiday-variants e GET …/employees/{employeeId}/payslips-to-update. Anche POST …/payslips/pdf-batch è paginato, ma il suo cursore viaggia nel corpo, in cursor.

3. Date e identificativi

Le date sono accettate in millisecondi epoch o in YYYY-MM-DD e restituite in millisecondi epoch. I filtri from/to delimitano la data di emissione, estremi inclusi. Due eccezioni: le assenze e le date di un dipendente (startDate, endDate, dateOfBirth) sono stringhe YYYY-MM-DD, sia in ingresso sia in uscita.

Gli identificativi sono stringhe opache. Nel percorso, un identificativo sconosciuto, uno malformato e uno di un'altra società rispondono tutti 404 NOT_FOUND: i tre casi sono volutamente indistinguibili, perché nessun identificativo di un altro account trapeli. Nel corpo, un riferimento a un'altra società risponde in base alla rotta: 403 ACCESS_DENIED su una fattura o un preventivo, 422 CLIENT_NOT_FOUND o 422 TEMPLATE_NOT_FOUND su una ricorrenza, 422 EMPLOYEE_NOT_FOUND su un'assenza.

4. Corpi stretti

  • I corpi JSON sono stretti: qualsiasi chiave sconosciuta risponde 400 VALIDATION_ERROR. Sulla creazione e sulla modifica di un dipendente e sulla creazione di una busta paga, il rifiuto proviene dal controllo lato server e indica il campo in message, senza issues. I corpi di …/laa-periods e …/avs-choices, invece, ignorano le chiavi sconosciute.
  • Un corpo è sempre richiesto su POST, PUT e PATCH ({} è una patch vuota valida; un corpo vuoto è INVALID_JSON), tranne sulle azioni senza corpo come POST …/default, …/convert, …/cancel-payment o …/payslips/{payslipId}/send; DELETE non ne accetta. Attenzione: POST …/payslips/{payslipId}/validate richiede almeno {}.
  • Oltre 1 MB, il corpo è rifiutato: 413 PAYLOAD_TOO_LARGE.
  • Sette rotte accettano byte grezzi anziché un JSON. POST …/expenses/{expenseId}/document, POST …/expense-drafts/extract, POST …/uploads/expense e POST …/uploads/invoice-import: PDF, PNG, JPEG o WebP, 10 MB. POST …/uploads/invoice-source: PNG, JPEG o WebP, 8 MB. PUT …/logo su una società e su un modello di fattura: solo PNG o JPEG, 2 MB. Il Content-Type dichiara il formato e il corpo è il file; tutto il resto viaggia nella query. Un altro formato risponde 415 UNSUPPORTED_MEDIA_TYPE, un corpo vuoto 400 EMPTY_BODY.
  • Un caricamento …/uploads/… restituisce un uploadId valido per un'ora. Oggi solo POST …/expense-drafts ne consuma uno: nessuna rotta pubblica accetta quelli di invoice-source e invoice-import.
  • Una chiave di query sconosciuta è un 400 VALIDATION_ERROR su ogni rotta che dichiara un contratto di query; le altre, come GET /me e GET /companies, ignorano il resto, senza che sia una promessa.
  • PUT su un'assenza è una sostituzione completa: i campi facoltativi omessi vengono cancellati. PUT su una fattura o un preventivo sostituisce tutte le righe e richiede expectedVersion.

Un errore di validazione elenca ogni campo errato sotto issues, in percorsi puntati, indici di array compresi:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid request body",
    "issues": [
      { "path": "subtotal", "message": "Unrecognized key \"subtotal\"" },
      { "path": "vatRates.0.rate", "message": "Too big: expected number to be <=100" }
    ]
  }
}

5. Errori

Gli errori portano un codice stabile, leggibile da una macchina:

{ "error": { "code": "PAYSLIP_ALREADY_EXISTS" } }

VALIDATION_ERROR aggiunge issues, oppure un message per i campi controllati lato server. Anche INVALID_TRANSITION, BATCH_SIZE_INVALID e UNSUPPORTED_MEDIA_TYPE aggiungono un message, e PAYROLL_CUMULATIVE_INCOMPLETE un details.months; ogni altro codice risponde da solo. Basa la tua logica su error.code, mai sul testo. Questa busta vale su ogni percorso sotto /api/v1/, compreso un percorso sconosciuto (404 NOT_FOUND) e un metodo non supportato (405 METHOD_NOT_ALLOWED, con un header Allow).

I controlli vengono eseguiti in quest'ordine: percorso e metodo (404, 405), dimensione del corpo (413), chiave e proprietario (401), visibilità della società (404), poi società archiviatapianomodulopermesso (403). Seguono la validazione del corpo e della query (400) e poi le regole di business. Una società di cui chi detiene la chiave non è membro è indistinguibile da una società inesistente.

  • 400 INVALID_JSON, 400 VALIDATION_ERROR: corpo assente o illeggibile, campo non valido. 400 BATCH_SIZE_INVALID: payslips/batch o bank-transactions/batch fuori dall'intervallo da 1 a 100 elementi; gli altri lotti sono limitati dal loro schema e rispondono VALIDATION_ERROR.
  • 401 INVALID_API_KEY: chiave assente, malformata, sconosciuta, revocata o scaduta; proprietario bannato o eliminato.
  • 402 SUBSCRIPTION_REQUIRED: creazione di un mandato senza un abbonamento fiduciario attivo.
  • 403 PLAN_NOT_ALLOWED, 403 COMPANY_ARCHIVED, 403 ACCESS_DENIED, 403 ACCOUNT_DISABLED: piano, archiviazione, permesso o ruolo mancanti, account disattivato.
  • 403 INVOICING_MODULE_DISABLED, 403 PAYROLL_MODULE_DISABLED, 403 ACCOUNTING_MODULE_DISABLED, 403 PRODUCT_CATALOG_DISABLED: modulo o catalogo disattivato sulla società. 403 LEDGER_DISABLED, 403 FINANCIAL_STATEMENTS_REPORTING_DISABLED: funzione chiusa sul deployment.
  • 403 *_LIMIT_REACHED: quota del piano raggiunta (clienti, prodotti, elenchi di prodotti, fatture, ricorrenze, dipendenti, buste paga, spese). 403 EXPENSE_AI_QUOTA_REACHED, 403 AI_QUOTA_REACHED: quote IA. 403 CURRENCY_PRO_ONLY: valuta diversa dal CHF con i limiti Starter.
  • 403 FILE_ACCESS_DENIED, 403 UPLOAD_EXPIRED: uploadId di un altro utente, già utilizzato o scaduto.
  • 409 INVALID_TRANSITION, 409 PAYSLIP_ALREADY_EXISTS, 409 PAYSLIP_DECLARED, 409 ABSENCE_OVERLAPS_EXISTING, 409 QUOTE_NOT_EDITABLE, 409 BILL_PAID_LOCKED, 409 BILL_NOT_PAID, 409 DOCUMENT_ALREADY_ATTACHED, 409 EXPENSE_DRAFT_LIMIT_REACHED: conflitto con lo stato attuale. 409 BILL_VERSION_CONFLICT, 409 QUOTE_VERSION_CONFLICT: il documento è cambiato dalla tua ultima lettura.
  • 422 MODIFICATION_REASON_REQUIRED, 422 REASON_REQUIRED, 422 CLIENT_EMAIL_MISSING, 422 EMPLOYEE_EMAIL_MISSING, 422 CLASSIFICATION_ACCOUNT_REQUIRED, 422 VALID_UNTIL_IN_PAST, 422 SCAN_UNREADABLE: richiesta valida ma non ammissibile in questo contesto.
  • 429 AI_RATE_LIMITED: il modello di IA limita le chiamate, riprova. 429 MANDATE_REQUEST_LIMIT_REACHED: troppe richieste di mandato.
  • 503 PDF_UNAVAILABLE, 502 EMAIL_SEND_FAILED, 502 AI_REQUEST_FAILED, 500 INTERNAL_ERROR: riprova. Una sola eccezione: dopo 502 QUOTE_EMAIL_SENT_TRANSITION_FAILED il preventivo è già partito, non inviarlo di nuovo.

La Swagger UI e openapi.json del tuo deployment pubblicano i codici dichiarati, rotta per rotta. Restano possibili altri codici di business: prevedi sempre un caso predefinito su error.code.

6. Limiti e utilizzo

Non c'è ancora alcuna limitazione di frequenza. Le chiamate vengono contate, non frenate: un contatore per chiave, endpoint, società interessata e giorno UTC, visibile in Il mio accountChiavi APIUtilizzo. Le richieste respinte dall'autenticazione non vengono contate; un 400 su una chiamata autorizzata sì.

Comportati comunque da buon vicino: scorri le liste invece di interrogarle in loop e rallenta su 500 o 503. I lotti non si comportano tutti allo stesso modo. POST …/payslips/batch non è atomico e restituisce un risultato per elemento. POST …/bank-transactions/batch, POST …/clients/batch e POST …/{invoices,quotes,payslips}/bulk-delete sono atomici, tutto o niente. Le rotte bulk-* di spese e transazioni bancarie, e …/expense-drafts/accept e …/reject, elaborano ciò che possono e conteggiano il resto in skipped.

7. Sandbox e reset

La sandbox non scrive mai in produzione. E-mail, Stripe e inviti al team vi sono inerti, la registrazione è chiusa, i loghi non vengono copiati. Tutto il resto funziona, compresi i job pianificati: le fatture ricorrenti vengono generate e le buste paga calcolate sui dati copiati. Il libro mastro vi è aperto, a differenza della produzione, dove le sue rotte rispondono 403 LEDGER_DISABLED.

Torna ai percorsi: Iniziare con l'API, Fatturare con l'API, Paghe, spese e contabilità con l'API, Ciclo paghe con l'API. Ogni campo e ogni risposta si trovano nella Swagger UI del tuo deployment (…/api/docs).