API e webhook
Per collegare V-Engyne al programma di contabilità, a un sito, a un gestionale o a un'automazione.
Come si comincia
L'API è in JSON, all'indirizzo https://v-engyne.com/api/v1. Il titolare dello studio crea una chiave in Account, Integrazioni: solo lettura, oppure anche scrittura. La chiave si vede una volta sola; chi la perde ne crea un'altra, e revocandola smette di funzionare subito. Ogni chiave vede soltanto i dati del suo studio.
Ogni chiamata porta la chiave nell'intestazione Authorization: Bearer vk_…. Al massimo 300 chiamate al minuto per chiave (poi risponde 429). Chi prova chiavi sbagliate a raffica viene fermato per un'ora. La chiave è un segreto: va solo nei programmi sul server, mai in una pagina web o in un'app che gli altri possono aprire.
Leggere i dati
| Indirizzo | Cosa restituisce |
|---|---|
| GET /api/v1/commesse | Commesse |
| GET /api/v1/clienti | Clienti |
| GET /api/v1/fatture | Fatture emesse (senza il file XML) · filtro modificato_dopo |
| GET /api/v1/fatture-ricevute | Fatture dei fornitori · filtro modificato_dopo |
| GET /api/v1/ore | Ore registrate · filtro modificato_dopo |
| GET /api/v1/spese | Note spese (senza la foto della ricevuta) |
| GET /api/v1/opportunita | Opportunità commerciali · filtro modificato_dopo |
| GET /api/v1/preventivi | Preventivi · filtro modificato_dopo |
| GET /api/v1/<risorsa>/<id> | Una riga sola (le fatture con le righe del documento) |
| GET /api/v1/contabilita | I registri per il commercialista di un periodo |
Gli elenchi vanno a pagine: limite (da 1 a 200, di base 50) e cursore, che è il prossimo_cursore dell'ultima risposta (quando è null sei in fondo). Dove c'è modificato_dopo (data e ora ISO) ottieni solo ciò che è cambiato da allora: così si tiene allineato un altro programma senza rileggere tutto.
curl "https://v-engyne.com/api/v1/fatture?limite=2&modificato_dopo=2026-10-01T00:00:00Z" \ -H "Authorization: Bearer vk_LA_TUA_CHIAVE"
{
"dati": [
{ "id": "5c1f…", "numero_fattura": "12/2026", "data_emissione": "2026-10-03",
"totale_documento": 1220, "stato_pagamento": "incassata", "…": "…" },
{ "id": "9a42…", "numero_fattura": "13/2026", "…": "…" }
],
"prossimo_cursore": "9a42…"
}Gli errori hanno sempre la stessa forma, { "errore": { "codice": "…", "messaggio": "…" } }, con lo stato HTTP giusto: 401 chiave non valida, 403 chiave di sola lettura o abbonamento non attivo, 404 non trovato, 422 dati non validi, 429 troppe chiamate.
I limiti, per ogni chiave: 300 chiamate al minuto, di cui al massimo 30 scritture al minuto e 500 al giorno, e i registri per il commercialista 10 volte al minuto. Dopo 30 chiavi sbagliate in un'ora dalla stessa rete, quella rete aspetta un'ora. Oltre un limite la risposta è 429, con l'intestazione Retry-After che dice fra quanti secondi riprovare.
Creare un'opportunità
Con una chiave che può scrivere, POST /api/v1/opportunita crea un'opportunità da un altro programma (il modulo di un altro sito, un gestionale, un'automazione). Serve titolo e almeno referente o azienda; si possono aggiungere tipo ("incarico" o "preventivo"), email, telefono, valore in euro e descrizione. L'opportunità arriva con origine "API" e gli admin ricevono una notifica.
curl -X POST "https://v-engyne.com/api/v1/opportunita" \
-H "Authorization: Bearer vk_LA_TUA_CHIAVE" \
-H "Content-Type: application/json" \
-d '{ "titolo": "Ristrutturazione villa", "tipo": "preventivo",
"referente": "Mario Rossi", "email": "mario@esempio.it",
"telefono": "333 1234567", "valore": 18000 }'Registri per il commercialista
GET /api/v1/contabilita?da=AAAA-MM-GG&a=AAAA-MM-GG restituisce in JSON i registri del periodo: fatture emesse, fatture ricevute, incassi, costi di commessa, note spese e IVA mese per mese. Con registro= (fatture_emesse, fatture_ricevute, incassi, costi_commessa, note_spese, iva_mensile) e formato=csv scarichi un file per Excel. Il periodo è al massimo di 3 anni. Banca e compensi non sono inclusi.
curl "https://v-engyne.com/api/v1/contabilita?da=2026-07-01&a=2026-09-30®istro=fatture_emesse&formato=csv" \ -H "Authorization: Bearer vk_LA_TUA_CHIAVE" -o fatture-emesse.csv
Webhook: essere avvisati
Un webhook è un indirizzo https del tuo programma che V-Engyne chiama quando succede qualcosa. Il titolare lo aggiunge in Account, Integrazioni, e sceglie gli eventi (o tutti). La chiamata è un POST con un contenuto corto: l'evento e gli identificativi, il resto si legge dall'API.
| Evento | Quando |
|---|---|
| commessa.creata | Nasce una commessa |
| commessa.stato_cambiato | Una commessa cambia stato |
| fattura.creata | Si crea una fattura |
| fattura.emessa | Una fattura prende il numero |
| fattura.pagata | Una fattura risulta incassata |
| opportunita.creata | Arriva un'opportunità |
| opportunita.stato_cambiato | Un'opportunità cambia stato |
| preventivo.stato_cambiato | Un preventivo cambia stato |
| spesa.stato_cambiato | Una nota spese cambia stato |
| richiesta.ricevuta | Arriva una richiesta dal modulo del sito |
| contratto.firma_cambiata | Cambia lo stato di firma di un contratto |
POST https://esempio.it/webhook/v-engyne
Content-Type: application/json
V-Engyne-Evento: fattura.pagata
V-Engyne-Consegna: 3f0c…
V-Engyne-Firma: t=1761300000,v1=9d2b…
{
"id": "e1c7…",
"tipo": "fattura.pagata",
"creato": "2026-10-24T09:00:00.000Z",
"dati": { "id": "5c1f…", "project_id": "77a0…", "numero_fattura": "12/2026" }
}Rispondi con uno stato 2xx entro 8 secondi: la chiamata è consegnata. Altrimenti V-Engyne riprova, fino a 8 volte, dopo 1 min, 5 min, 30 min, 2 h, 6 h, 12 h, 24 h. La stessa chiamata può arrivare più di una volta: l'id nel contenuto (e V-Engyne-Consegna) serve a riconoscerla. Gli indirizzi devono essere raggiungibili da internet: V-Engyne non chiama reti private e non segue i reindirizzamenti.
Controllare la firma
Ogni chiamata ha l'intestazione V-Engyne-Firma: t=<secondi>,v1=<firma>. La firma è l'HMAC SHA-256 di t, un punto e il contenuto così com'è arrivato, calcolato con il segreto del webhook. Controllala prima di fidarti della chiamata, e scarta quelle con t di più di 5 minuti fa.
import { createHmac, timingSafeEqual } from 'node:crypto'
// segreto: quello mostrato quando hai creato il webhook; corpo: il testo
// ricevuto, prima di leggerlo come JSON
function firmaValida(segreto, intestazione, corpo) {
const t = /t=(\d+)/.exec(intestazione)?.[1]
const v1 = /v1=([0-9a-f]{64})/.exec(intestazione)?.[1]
if (!t || !v1) return false
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false // oltre 5 minuti
const atteso = createHmac('sha256', segreto).update(t + '.' + corpo).digest()
return timingSafeEqual(atteso, Buffer.from(v1, 'hex'))
}