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'accessToken scade dopo 1 ora. Usa il refreshToken per 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 errore 409 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 fiscale
  • dateFrom, dateTo — Range date (YYYY-MM-DD)
  • search — Ricerca per descrizione/numero
  • page, 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
}