API Reference
Lema espone un'API REST completa per integrazioni e automazioni.
Autenticazione
Tutte le richieste richiedono un JWT token nell'header Authorization:
curl -H "Authorization: Bearer YOUR_JWT_TOKEN" \
https://lema.arcaweb.ch/api/entities/contacts
Ottenere un Token (Login)
curl -X POST https://lema.arcaweb.ch/api/auth/global/login \
-H "Content-Type: application/json" \
-d '{"email": "mario@example.com", "password": "MyPassword123!"}'
Risposta:
{
"user": {
"id": "uuid",
"name": "Mario Rossi",
"email": "mario@example.com",
"role": "admin"
},
"accessToken": "eyJhbGc...",
"refreshToken": "eyJhbGc...",
"expiresIn": 3600,
"tenantId": "tnt-abc123",
"tenantName": "Acme Srl",
"tenants": [
{ "id": "tnt-abc123", "name": "Acme Srl", "role": "admin", "isDefault": true }
]
}
Nota: L'
accessTokenscade dopo 1 ora. Usa ilrefreshTokenper ottenerne uno nuovo.
Base URL
| Ambiente | URL |
|---|---|
| Cloud | https://lema.arcaweb.ch/api |
| Locale (sviluppo) | http://localhost:3001/api |
| Electron embedded | http://localhost:31847/api |
Endpoint di Autenticazione
Login
POST /api/auth/global/login
Content-Type: application/json
{
"email": "mario@example.com",
"password": "MyPassword123!"
}
Logout
POST /api/auth/global/logout
Authorization: Bearer <access_token>
{
"refreshToken": "..."
}
Refresh Token
POST /api/auth/global/refresh
Content-Type: application/json
{
"refreshToken": "..."
}
Switch Tenant
Per operare su un altro tenant (se l'utente ha accesso a più tenant):
POST /api/auth/global/switch-tenant
Authorization: Bearer <access_token>
Content-Type: application/json
{
"tenantId": "tnt-other123"
}
Risposta: nuovi token con il tenant selezionato.
Cambia Password
POST /api/auth/global/change-password
Authorization: Bearer <access_token>
Content-Type: application/json
{
"currentPassword": "OldPassword123!",
"newPassword": "NewPassword456!"
}
Endpoint Generici (Entities)
Lema usa endpoint REST generici per tutte le entità:
Lista entità
GET /api/entities/{table}?page=1&pageSize=20&sortField=name&sortDir=asc
Parametri query:
| Parametro | Tipo | Default | Descrizione |
|---|---|---|---|
page |
number | 1 | Pagina corrente |
pageSize |
number | 20 | Elementi per pagina |
sortField |
string | created_at |
Campo ordinamento |
sortDir |
string | desc |
Direzione: asc o desc |
search |
string | - | Ricerca full-text |
filter[campo] |
string | - | Filtro per campo specifico |
Risposta:
{
"data": [
{ "id": "uuid-1", "name": "Cliente 1" },
{ "id": "uuid-2", "name": "Cliente 2" }
],
"pagination": {
"page": 1,
"pageSize": 20,
"total": 150,
"totalPages": 8
}
}
Singola entità
GET /api/entities/{table}/{id}
Creare entità
POST /api/entities/{table}
Content-Type: application/json
{
"name": "Nuovo Cliente",
"email": "nuovo@example.com"
}
Aggiornare entità
PUT /api/entities/{table}/{id}
Content-Type: application/json
{
"name": "Nome Aggiornato",
"version": 1
}
Optimistic Locking: Il campo
versionè obbligatorio. Se la versione non corrisponde, riceverai errore409 Conflict.
Eliminare entità
DELETE /api/entities/{table}/{id}
Gestione Utenti (TEAM/ENTERPRISE)
I piani TEAM ed ENTERPRISE supportano più utenti. Gli utenti vengono aggiunti tramite il sistema di inviti.
Lista utenti
GET /api/users
Risposta:
{
"users": [
{
"id": "uuid",
"name": "Mario Rossi",
"email": "mario@example.com",
"role": "admin",
"is_active": true
}
]
}
Invita utente
Gli utenti non vengono creati direttamente, ma invitati via email:
POST /api/users/invite
Content-Type: application/json
{
"email": "nuovo@example.com",
"role": "employee",
"permissions": ["contacts.view", "contacts.edit", "invoices.view"]
}
Ruoli disponibili: owner, direction, sales, accountant, employee, warehouse, technician, hr, external
Risposta:
{
"success": true,
"type": "new_user",
"invitation": {
"id": "inv-uuid",
"email": "nuovo@example.com",
"role": "employee",
"expires_at": "2026-02-09T12:00:00.000Z"
}
}
L'utente riceverà un'email con un link per completare la registrazione.
Lista inviti pendenti
GET /api/users/invites
Annulla invito
DELETE /api/users/invites/{id}
Reinvia invito
POST /api/users/invites/{id}/resend
I miei inviti pendenti
Restituisce gli inviti ricevuti dall'utente corrente:
GET /api/users/my-invitations
Risposta:
{
"invitations": [
{
"id": "inv-uuid",
"tenantId": "tnt-abc123",
"tenantName": "Acme Srl",
"role": "employee",
"invitedByName": "Mario Rossi"
}
]
}
Accetta invito
POST /api/users/invitations/{id}/accept
Rifiuta invito
POST /api/users/invitations/{id}/decline
Reset password
POST /api/users/{id}/reset-password
Risposta:
{
"temporaryPassword": "TempPass123!",
"mustChangePassword": true
}
Tabelle Disponibili
| Tabella | Descrizione |
|---|---|
contacts |
Clienti e fornitori |
contact_groups |
Gruppi contatti |
products |
Prodotti e servizi |
product_types |
Categorie prodotti |
invoices |
Fatture |
invoice_lines |
Righe fattura |
contracts |
Contratti |
calendar_events |
Eventi calendario |
vat_rates |
Aliquote IVA |
Esempi Pratici
Ricerca contatti
curl -G "http://localhost:3001/api/entities/contacts" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
--data-urlencode "search=rossi" \
--data-urlencode "filter[type]=customer" \
--data-urlencode "filter[status]=active"
Creare fattura con righe
# 1. Crea fattura
curl -s -X POST "http://localhost:3001/api/entities/invoices" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"contact_id": "customer-uuid",
"date": "2024-01-15",
"due_date": "2024-02-14",
"status": "draft"
}'
# 2. Aggiungi righe (usa l'id della fattura creata)
curl -X POST "http://localhost:3001/api/entities/invoice_lines" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"invoice_id": "INVOICE_ID",
"description": "Consulenza tecnica",
"quantity": 8,
"unit": "ora",
"unit_price": 120.00,
"vat_rate": 8.1
}'
Login con Username
Si può usare lo username al posto dell'email:
curl -X POST "http://localhost:3001/api/auth/global/login" \
-H "Content-Type: application/json" \
-d '{"email": "mario", "password": "MyPassword123!"}'
Errori
| Codice | Significato |
|---|---|
400 |
Bad Request - Dati non validi |
401 |
Unauthorized - Token JWT mancante/invalido |
403 |
Forbidden - Licenza scaduta o permessi insufficienti |
404 |
Not Found - Risorsa non trovata |
409 |
Conflict - Conflitto di versione (optimistic lock) |
422 |
Unprocessable Entity - Validazione fallita |
500 |
Internal Server Error |
Formato errore:
{
"error": "Credenziali non valide",
"code": "INVALID_CREDENTIALS"
}
Real-time Sync (SSE)
const eventSource = new EventSource(
'http://localhost:3001/api/events',
{ headers: { 'Authorization': 'Bearer YOUR_JWT_TOKEN' } }
);
eventSource.addEventListener('entity:created', (e) => {
const data = JSON.parse(e.data);
console.log('Nuova entità:', data.table, data.id);
});
eventSource.addEventListener('entity:updated', (e) => {
const data = JSON.parse(e.data);
console.log('Entità aggiornata:', data.table, data.id);
});
Rate Limiting
- Locale: Nessun limite
- Cloud: 1000 richieste/minuto per utente
Autenticazione Globale (Account)
Oltre all'autenticazione per tenant, Lema supporta un sistema di autenticazione globale per la gestione dell'account utente e delle licenze.
Registrazione
POST /api/auth/global/register
Content-Type: application/json
{
"email": "user@example.com",
"password": "SecurePassword123!",
"name": "Mario Rossi"
}
Risposta:
{
"user": {
"id": "uuid",
"email": "user@example.com",
"name": "Mario Rossi"
},
"accessToken": "eyJhbGc...",
"refreshToken": "eyJhbGc...",
"expiresIn": 900
}
Login Globale
POST /api/auth/global/login
Content-Type: application/json
{
"email": "user@example.com",
"password": "SecurePassword123!"
}
Recupero Password
POST /api/auth/global/forgot-password
Content-Type: application/json
{
"email": "user@example.com"
}
In sviluppo, il token viene restituito nella risposta. In produzione, viene inviato via email.
Reset Password
POST /api/auth/global/reset-password
Content-Type: application/json
{
"token": "reset-token-from-email",
"newPassword": "NewSecurePassword456!"
}
Cambio Password
POST /api/auth/global/change-password
Authorization: Bearer <access_token>
Content-Type: application/json
{
"currentPassword": "OldPassword123!",
"newPassword": "NewPassword456!"
}
Gestione Licenze
Stato Subscription
GET /api/license/subscription
Authorization: Bearer <access_token>
Risposta:
{
"plan": "pro",
"status": "active",
"current_period_end": "2025-02-15T00:00:00Z",
"limits": {
"maxTenants": 3,
"maxSeats": 5,
"maxContacts": 10000,
"maxDocumentsPerMonth": 500
},
"usage": {
"tenants": 1,
"seats": 2,
"contacts": 156,
"documentsThisMonth": 23
}
}
Dispositivi Registrati
GET /api/license/devices
Authorization: Bearer <access_token>
Risposta:
{
"devices": [
{
"id": "uuid",
"name": "MacBook Pro",
"platform": "darwin",
"last_active": "2025-01-15T10:30:00Z",
"current": true
}
],
"limit": 3,
"used": 1
}
Revoca Dispositivo
DELETE /api/license/devices/{deviceId}
Authorization: Bearer <access_token>
Revoca Tutti i Dispositivi
Revoca tutti i dispositivi tranne quello corrente (per "disconnetti tutte le sessioni"):
POST /api/license/devices/revoke-all
Authorization: Bearer <access_token>
Risposta:
{
"message": "Revocati 3 dispositivi",
"revokedCount": 3
}
Genera License Token
POST /api/license/token
Authorization: Bearer <access_token>
Content-Type: application/json
{
"deviceId": "unique-device-id",
"deviceName": "MacBook Pro",
"platform": "darwin"
}
Risposta:
{
"licenseToken": "eyJhbGc...",
"expiresAt": "2025-02-15T00:00:00Z"
}
Il license token è firmato RSA e può essere verificato offline dal client.
🔧 Dev Mode: Cambia Piano (Solo Sviluppo)
Durante lo sviluppo, senza integrazione Stripe, è possibile cambiare piano istantaneamente:
POST /api/license/dev/set-plan
Authorization: Bearer <access_token>
Content-Type: application/json
{
"plan": "pro"
}
⚠️ Questo endpoint è disponibile SOLO quando
NODE_ENV !== 'production'.
Contabilità /api/accounting
Endpoint per il modulo contabilità con supporto revisione contabile.
Giornale Generale
GET /api/accounting/general-journal?fiscalYearId=fy_2024&page=1&pageSize=50
Authorization: Bearer <access_token>
Parametri query:
fiscalYearId— Esercizio fiscaledateFrom,dateTo— Range date (YYYY-MM-DD)search— Ricerca per descrizione/numeropage,pageSize— Paginazione
Risposta:
{
"data": [
{
"id": "je_abc123",
"entry_number": "2024/0042",
"date": "2024-03-15",
"description": "Incasso fattura",
"status": "posted",
"lines": [
{ "account_id": "acc_1020", "_account_name": "Banca", "debit": 1000, "credit": 0 },
{ "account_id": "acc_1100", "_account_name": "Crediti", "debit": 0, "credit": 1000 }
]
}
],
"total": 150,
"page": 1,
"pageSize": 50
}
Mastro Conto
GET /api/accounting/account-ledger?accountId=acc_1020&fiscalYearId=fy_2024
Authorization: Bearer <access_token>
Risposta:
{
"data": [
{
"date": "2024-01-05",
"entry_number": "2024/0003",
"description": "Incasso",
"debit": 1200,
"credit": 0,
"_running_balance": 6400
}
],
"total": 45,
"openingBalance": 5200,
"closingBalance": 6600
}
Storno Registrazione
POST /api/accounting/journal/{id}/reverse
Authorization: Bearer <access_token>
Content-Type: application/json
{
"reason": "Errore importo"
}
Crea una registrazione inversa (Dare↔Avere) collegata all'originale. La motivazione è obbligatoria.
Periodi Contabili
GET /api/accounting/periods?fiscalYearId=fy_2024
POST /api/accounting/periods
PUT /api/accounting/periods/{id}
DELETE /api/accounting/periods/{id}
Authorization: Bearer <access_token>
Gestione periodi contabili (mensili, trimestrali) con stati: open, closed, locked.
Registro Modifiche (Audit Log)
GET /api/accounting/audit-log?action=reverse&page=1&pageSize=50
Authorization: Bearer <access_token>
Registro immutabile di tutte le operazioni contabili. Filtri: entityTable, action, dateFrom, dateTo.
Controlli di Integrità
GET /api/accounting/health-checks?fiscalYearId=fy_2024
Authorization: Bearer <access_token>
Risposta:
{
"checks": [
{ "check": "balance_check", "status": "ok", "severity": "success", "message": "Tutte le registrazioni bilanciano" },
{ "check": "sequence_check", "status": "warning", "severity": "warning", "message": "2 gap nella numerazione" }
]
}
Export Pacchetto Revisione
GET /api/accounting/export/audit-package?fiscalYearId=fy_2024
Authorization: Bearer <access_token>
Esporta un pacchetto JSON completo contenente: piano conti, registrazioni, periodi, audit log, controlli integrità e metadati. Ideale per il revisore.
Protezione Immutabilità
Le registrazioni confermate (status = posted) non possono essere modificate o eliminate. Per correggere errori, usare lo storno (POST /journal/{id}/reverse). Il registro audit log è append-only e non modificabile.
Backup /api/backup
API per la gestione dei backup del tenant. Richiede JWT con permesso backup.view (lettura) o backup.manage (scrittura).
Lista backup
GET /api/backup
Authorization: Bearer <access_token>
Risposta:
{
"data": [
{
"id": "backup_manual_2026-03-05_14-30-00.db",
"filename": "backup_manual_2026-03-05_14-30-00.db",
"type": "db",
"trigger": "manual",
"size": 2457600,
"createdAt": "2026-03-05T14:30:00.000Z"
},
{
"id": "backup_auto_2026-03-04_02-00-00.tar.gz",
"filename": "backup_auto_2026-03-04_02-00-00.tar.gz",
"type": "full",
"trigger": "auto",
"size": 15728640,
"createdAt": "2026-03-04T02:00:00.000Z"
}
]
}
Crea backup manuale
POST /api/backup?type=db
Authorization: Bearer <access_token>
| Parametro | Tipo | Default | Descrizione |
|---|---|---|---|
type |
query | db |
Tipo backup: db (solo database) o full (database + uploads) |
Download backup
GET /api/backup/:id/download
Authorization: Bearer <access_token>
Restituisce il file binario con gli header appropriati per il download.
Upload backup
POST /api/backup/upload
Authorization: Bearer <access_token>
Content-Type: multipart/form-data
Carica un file di backup esterno. Formati accettati: .db, .tar.gz. Dimensione massima: 500MB.
curl -X POST "https://lema.arcaweb.ch/api/backup/upload" \
-H "Authorization: Bearer <token>" \
-F "file=@backup.db"
Ripristina backup
POST /api/backup/:id/restore
Authorization: Bearer <access_token>
Ripristina il database dal backup selezionato. Il sistema crea automaticamente un backup di sicurezza prima del ripristino.
⚠️ Attenzione: il ripristino sostituisce tutti i dati correnti del tenant.
Elimina backup
DELETE /api/backup/:id
Authorization: Bearer <access_token>
Impostazioni backup (read-only)
GET /api/backup/settings
Authorization: Bearer <access_token>
Restituisce le impostazioni di backup globali. I tenant possono visualizzarle ma non modificarle (la configurazione è gestita dal super admin).
{
"dbCron": "0 2 * * *",
"fullBackupEvery": 7,
"maxDbBackups": 30,
"maxFullBackups": 4,
"enabled": true,
"readOnly": true
}