1Cosa puoi fare
Le API v1 coprono il ciclo della fattura di vendita: crearla, trasmetterla al Sistema di Interscambio, seguirne l’esito e scaricarne l’XML. Sono pensate per casi come questi:
- un e-commerce che fattura in automatico ogni ordine pagato;
- un gestionale di magazzino o di produzione che emette la fattura a fine lavoro;
- un software di prenotazioni che fattura il servizio erogato;
- un’automazione (Zapier, Make, n8n, script interni) che chiude il ciclo.
Cosa non fanno, in questa versione. Non creano clienti o prodotti, non modificano né cancellano documenti, non gestiscono acquisti, DDT o preventivi. Anagrafiche e correzioni restano nel gestionale, dove ci sono i controlli del caso.
2Prima di iniziare
Serve che nel gestionale sia già a posto quello che servirebbe comunque per fatturare a mano.
Se manca qualcosa qui, l’API se ne accorge e risponde con un errore 422.
| Cosa | Dove si controlla | Perché serve |
|---|---|---|
| Dati azienda completi partita IVA, regime fiscale, IBAN |
Impostazioni › Preferenze Generali | finiscono nell’XML della fattura elettronica |
| Aliquote IVA usate dal sistema esterno | Impostazioni › Avanzate › Tasse & IVA | l’API rifiuta un’aliquota non configurata |
| Clienti in anagrafica con codice destinatario o PEC |
Persone › Clienti | si fattura a un cliente che esiste già |
| Autorizzazione all’invio al SDI | riquadro in cima alla pagina Vendite › Fatture | senza, l’XML si genera ma non parte |
L’ultimo punto è quello che blocca più spesso. Finché non ci risulta pervenuto
il modulo firmato che ci autorizza a trasmettere per tuo conto, le funzioni di invio restano
disattivate: la fattura si crea e l’XML si genera, ma POST /fatture/{id}/sdi non
la fa partire.
3La chiave API
Generarla
Impostazioni › Chiavi API → Genera chiave. Dai un nome che dica quale sistema è collegato (“E-commerce”, “Gestionale magazzino”, “Zapier”): serve a te il giorno in cui devi revocarne una sola senza fermare le altre.
La chiave in chiaro compare una volta sola. Sul server ne conserviamo solo l’impronta (SHA-256), esattamente come per le password: non possiamo rimostrartela, nemmeno noi. Se la perdi, ne generi un’altra e revochi la vecchia.
Che aspetto ha
La parte centrale dice alle API su quale azienda lavorare: per questo non devi indicare
l’azienda da nessun’altra parte. Il formato accettato è
cw_ + da 1 a 32 caratteri alfanumerici + _ + da 32 a 128 caratteri
alfanumerici; qualsiasi altra forma viene rifiutata prima ancora di interrogare il database.
Tenerla al sicuro
- Va messa solo nell’intestazione della richiesta, mai nell’indirizzo: gli indirizzi finiscono nei log dei server e nella cronologia dei browser.
- Conservala tra i segreti del sistema che la usa (variabile d’ambiente, vault), non dentro il codice sorgente versionato.
- Una chiave per ogni integrazione: revocarne una non ferma le altre.
- Se un fornitore esterno smette di lavorare con te, revoca la sua chiave: da quel momento
le sue richieste ricevono
403e non serve fare altro.
Ogni chiamata andata a buon fine aggiorna la colonna Ultimo utilizzo nella pagina delle chiavi: è il modo più rapido per capire se un’integrazione è ancora viva — o se una chiave che credevi ferma sta ancora lavorando.
4Il primo test
Prima di scrivere qualsiasi cosa, verifica che la chiave apra la porta giusta:
curl https://app.cofferweb.com/api/v1/ping \ -H "X-API-Key: cw_a43b5_LA_TUA_CHIAVE"
{
"ok": true,
"versione": "v1",
"azienda": "Rossi S.r.l.",
"chiave": "E-commerce"
}
Se leggi il nome della tua azienda e il nome che avevi dato alla chiave, il collegamento funziona: tutto il resto è questione di comporre i dati giusti.
5Anatomia di una richiesta
| Elemento | Valore |
|---|---|
| Indirizzo di base | https://app.cofferweb.com/api/v1/ |
| Autenticazione | X-API-Key: cw_… oppure Authorization: Bearer cw_… |
| Formato | JSON in ingresso e in uscita, sempre, anche per gli errori |
| Codifica | UTF-8 |
| Corpo | solo per POST; con Content-Type: application/json |
Se gli indirizzi “puliti” non sono attivi
Le stesse chiamate funzionano anche senza riscrittura degli indirizzi, in due forme equivalenti:
https://app.cofferweb.com/api/v1/index.php/fatture/812/sdi https://app.cofferweb.com/api/v1/index.php?percorso=fatture/812/sdi
Sul server, il file api/v1/_htaccess va rinominato in .htaccess per
abilitare gli indirizzi puliti. Contiene anche le due righe che inoltrano a PHP le intestazioni
Authorization e X-API-Key, che con CGI/FastCGI andrebbero altrimenti perse:
se ricevi 401 chiave_mancante pur mandando la chiave, il problema è quasi sempre lì.
6Elenco dei comandi
Chiamato senza percorso, risponde con la versione e l’elenco dei percorsi disponibili. Utile come verifica di raggiungibilità.
{
"servizio": "cofferweb API",
"versione": "v1",
"percorsi": ["ping", "anagrafiche", "fatture", "fatture/{id}",
"fatture/{id}/sdi", "fatture/{id}/xml", "fatture/{id}/notifica"]
}
Conferma che la chiave è valida e attiva, e dice su quale azienda sta lavorando. Non modifica nulla: è la chiamata da usare nei controlli automatici.
Restituisce quello che serve per comporre una fattura valida: le aliquote IVA configurate
in azienda, l’elenco clienti con il loro id, i codici dei metodi di pagamento
della fattura elettronica.
{
"aliquote_iva": [
{ "id": 3, "nome": "IVA 22%", "percentuale": 22 },
{ "id": 5, "nome": "IVA 10%", "percentuale": 10 }
],
"clienti": [
{ "id": 42, "codice": "CL0042", "nome": "Rossi S.r.l." }
],
"pagamenti_fe": {
"MP01": "contanti", "MP02": "assegno",
"MP05": "bonifico", "MP08": "carta di pagamento",
"MP12": "RIBA", "MP19": "SEPA Direct Debit"
}
}
Chiamalo una volta all’avvio della tua integrazione e tieni in cache
il risultato: cambia di rado. Rileggilo quando ricevi un errore
aliquota_non_trovata o cliente_non_trovato.
curl -X POST https://app.cofferweb.com/api/v1/fatture \
-H "X-API-Key: cw_a43b5_LA_TUA_CHIAVE" \
-H "Content-Type: application/json" \
-d '{
"cliente_id": 42,
"data": "26/07/2026",
"elettronica": true,
"pagamento_fe": "MP05",
"note": "Ordine web #10245",
"righe": [
{ "descrizione": "Abbonamento annuale", "quantita": 1, "prezzo": 250.00, "iva": 22 },
{ "descrizione": "Installazione", "quantita": 2, "prezzo": 80.00, "iva": 22, "sconto": 10 }
]
}'
Campi di testata
| Campo | Obbl. | Default | Note |
|---|---|---|---|
cliente_id | sì | — | id da /anagrafiche. Intestazione, indirizzi, partita IVA e listino vengono presi dall’anagrafica |
righe | sì | — | almeno una |
data | no | oggi | formato gg/mm/aaaa |
elettronica | no | true | false per una fattura non elettronica |
pagamento_fe | no | MP05 | codice SDI del metodo di pagamento |
iban | no | quello in Preferenze | usato nell’XML |
codice_destinatario | no | quello del cliente | 7 caratteri, per B2B |
pec_destinatario | no | quella del cliente | alternativa al codice destinatario |
codice_pa | no | — | se valorizzato la fattura diventa verso Pubblica Amministrazione |
nota_credito | no | false | true per una nota di credito |
riferimento_fattura | no | — | id della fattura a cui si riferisce la nota di credito |
esigibilita_iva | no | I | I immediata, D differita, S scissione dei pagamenti |
tipo_documento | no | 0 | tipo documento SDI (TD01, TD04…) quando serve forzarlo |
payment_id | no | quello del cliente | metodo di pagamento interno del gestionale |
banca_id | no | 0 | banca d’appoggio del documento |
note · note_piepagina · note_interne · causale |
no | vuoti | testi liberi |
Campi di riga
| Campo | Obbl. | Default | Note |
|---|---|---|---|
descrizione | sì | — | quello che il cliente legge in fattura |
quantita | no | 1 | |
prezzo | no | 0 | unitario, IVA esclusa |
iva | no | 0 | percentuale; deve corrispondere a un’aliquota configurata |
tax_id | no | — | in alternativa a iva, l’id preciso dell’aliquota. Se c’è, vince su iva |
sconto | no | 0 | percentuale sulla riga |
prodotto_id | no | 0 | collega la riga a un prodotto a catalogo |
Come vengono calcolati gli importi
Per ogni riga: quantita × prezzo, meno lo sconto percentuale, dà
l’imponibile; l’IVA è l’aliquota indicata applicata a quell’imponibile.
I totali del documento sono la somma delle righe, arrotondata a 2 decimali.
Numero e codice della fattura li assegna cofferweb secondo la numerazione impostata in azienda: non vanno passati, e non si possono forzare.
{ "fattura": {
"id": 812, "numero": 137, "codice": "FT137", "data": "26/07/2026",
"cliente": "Rossi S.r.l.",
"imponibile": 394.00, "iva": 86.68, "totale": 480.68,
"stato": "da_inviare", "inviata_al_sdi": false,
"data_invio_sdi": null, "notifica_sdi": null,
"notifica_descrizione": null, "file_xml": null
} }
Conserva l’id accanto al tuo ordine: è la chiave per
tutte le operazioni successive, ed è quello che ti salva se una chiamata va in timeout.
curl -X POST https://app.cofferweb.com/api/v1/fatture/812/sdi \ -H "X-API-Key: cw_a43b5_LA_TUA_CHIAVE"
Genera l’XML e lo trasmette, esattamente come il pulsante Invia a SDI
nella pagina Fatture. La risposta è l’oggetto fattura aggiornato, con
inviata_al_sdi: true.
Non è annullabile. Dopo l’invio il documento non è più
modificabile, né da API né a mano. Se richiami questo comando su una fattura già
trasmessa ricevi 409 gia_inviata: non è un guasto, è una protezione.
Se al documento mancano dati obbligatori (codice destinatario o PEC, metodo di pagamento,
IBAN) la risposta è 422 documento_incompleto e la fattura resta
modificabile: correggi e richiama.
L’oggetto fattura completo, più l’elenco delle righe con i rispettivi importi.
{ "fattura": {
"id": 812, "numero": 137, "codice": "FT137", "data": "26/07/2026",
"cliente": "Rossi S.r.l.",
"imponibile": 394.00, "iva": 86.68, "totale": 480.68,
"stato": "notificata", "inviata_al_sdi": true,
"data_invio_sdi": "26/07/2026 11:04",
"notifica_sdi": "RC",
"notifica_descrizione": "Ricevuta di consegna: la fattura e' stata consegnata al destinatario.",
"file_xml": "IT01234567890_00001.xml",
"righe": [
{ "descrizione": "Abbonamento annuale", "quantita": 1, "prezzo": 250,
"imponibile": 250, "iva": 55, "totale": 305 }
]
} }
{ "notifica": {
"tipo": "RC",
"descrizione": "Ricevuta di consegna: la fattura e' stata consegnata al destinatario.",
"contenuto": "PD94bWwg…" },
"fattura": { "id": 812, "stato": "notificata", … } }
contenuto è il file di notifica originale in base64, se vuoi archiviarlo;
vale null se il file non è (ancora) sul server.
Finché il SDI non ha risposto, notifica vale null
e la risposta è comunque 200. È il caso normale nei primi minuti:
non trattarlo come errore.
{ "nome_file": "IT01234567890_00001.xml",
"contenuto": "PD94bWwgdmVyc2lvbj0i…" }
Il contenuto è in base64: decodificalo per ottenere il file da archiviare o da consegnare
al cliente. Se l’XML non è ancora stato generato la risposta è
404 xml_non_disponibile.
Le fatture dell’anno in corso, dalla più recente, massimo
200. Serve soprattutto a riconciliare: se il tuo sistema ha perso un id,
lo ritrovi da qui invece di creare un doppione.
7L’oggetto fattura
È la stessa struttura in tutte le risposte che riguardano un documento.
| Campo | Tipo | Significato |
|---|---|---|
id | intero | identificativo interno: è quello da conservare |
numero | intero | numero progressivo della fattura |
codice | testo | codice completo del documento (es. FT137) |
data | testo | gg/mm/aaaa |
cliente | testo | intestazione del cliente |
imponibile · iva · totale | decimale | totali del documento |
stato | testo | vedi la tabella qui sotto |
inviata_al_sdi | booleano | se la trasmissione è avvenuta |
data_invio_sdi | testo o null | gg/mm/aaaa hh:mm |
notifica_sdi | testo o null | sigla dell’ultima notifica (RC, NS…) |
notifica_descrizione | testo o null | la stessa notifica spiegata in italiano |
file_xml | testo o null | nome del file XML generato |
righe | elenco | solo in GET /fatture/{id} |
8Stati e notifiche SDI
Il campo stato
| Stato | Significato | Cosa fare |
|---|---|---|
| da_inviare | creata, mai trasmessa | inviala quando vuoi; nel frattempo è modificabile |
| inviata | trasmessa, in attesa di risposta | aspetta la notifica |
| notificata | il SDI ha risposto | leggi notifica_sdi |
| scartata | scarto: la fattura non risulta emessa | correggi e riemetti |
Le sigle delle notifiche
| Codice | Cosa vuol dire | Serve fare qualcosa? |
|---|---|---|
RC | consegnata al destinatario | no |
DT | decorrenza termini: il destinatario non si è espresso | no, la fattura è valida |
MC | mancata consegna: valida, messa a disposizione nell’area riservata del cliente | avvisa il cliente, non riemettere |
AT | attestazione di trasmissione (destinatario estero o canali non disponibili) | trasmetti l’attestazione al cliente |
NS | scarto: la fattura non risulta emessa | correggi e ritrasmetti entro 5 giorni |
NE / EC | il destinatario ha accettato o rifiutato | se rifiutata, verifica il motivo |
SE | scarto dell’esito inviato dal committente | di norma nulla per chi emette |
MT | file dei metadati | no, è tecnica |
Il SDI risponde di solito in pochi minuti, ma per legge può impiegare fino a 5 giorni. Un’integrazione che considera fallita una fattura perché dopo un minuto non c’è ancora la notifica è un’integrazione che genera doppioni.
9Errori
Tutti gli errori hanno la stessa forma, e sono sempre JSON:
{ "errore": {
"codice": "aliquota_non_trovata",
"messaggio": "Riga 1: aliquota IVA 4% non configurata in azienda.",
"dettagli": [ { "id": 3, "nome": "IVA 22%", "percentuale": 22 } ] } }
codice è stabile ed è quello su cui scrivere la logica;
messaggio è per gli umani; dettagli compare quando c’è qualcosa
di utile da mostrare — per esempio l’elenco delle aliquote effettivamente configurate.
| Stato | Codice | Cosa è successo | Cosa fare |
|---|---|---|---|
| 400 | json_non_valido | il corpo non è JSON valido | controlla virgole e virgolette |
| 401 | chiave_mancante | non hai mandato l’intestazione | aggiungi X-API-Key; se ci sei sicuro, vedi la nota sull’.htaccess |
| 401 | chiave_non_valida | chiave sbagliata, storpiata o azienda inesistente | ricontrolla, eventualmente rigenerala |
| 403 | chiave_revocata | la chiave è stata revocata dal gestionale | usane una attiva |
| 404 | non_trovata | l’id non esiste per questa azienda | verifica l’id |
| 404 | xml_non_disponibile | XML non ancora generato, o file assente | invia prima al SDI |
| 404 | percorso_non_trovato | indirizzo scritto male | confronta con l’elenco dei comandi |
| 405 | metodo_non_consentito | metodo HTTP sbagliato su quel percorso | es. POST dove serve GET |
| 409 | gia_inviata | fattura già trasmessa al SDI | non reinviare: leggi lo stato |
| 422 | cliente_mancante / cliente_non_trovato | cliente_id assente o inesistente | prendi l’id da /anagrafiche |
| 422 | righe_mancanti / riga_non_valida | nessuna riga, o riga senza descrizione | completa i dati |
| 422 | aliquota_non_trovata | l’IVA richiesta non è configurata | usa una delle aliquote nei dettagli |
| 422 | documento_incompleto | mancano i dati per la fattura elettronica | aggiungi codice destinatario/PEC, pagamento, IBAN |
| 500 | errore_interno | la fattura non è stata creata | riprova; se persiste, apri un ticket con il messaggio |
| 502 | invio_fallito | problema nel dialogo con il SDI | riprova più tardi, poi apri un ticket |
Regola generale: 4xx dipende dai dati che hai mandato,
5xx da noi o dal SDI. Solo i 5xx ha senso ritentarli in automatico,
con attesa crescente. Riprovare identico un 422 non serve: manca un’informazione.
10Il flusso completo
Un e-commerce che fattura un ordine pagato, passo per passo:
1. (una volta) GET /anagrafiche -> memorizza id cliente e id aliquote 2. ordine pagato POST /fatture -> ottieni id = 812, stato "da_inviare" 3. subito dopo POST /fatture/812/sdi -> stato "inviata" 4. ogni 15 min GET /fatture/812/notifica -> finche' arriva una notifica 5. se RC/DT/MC archivia l'XML e chiudi l'ordine se NS segnala all'amministrazione: va corretta e riemessa
11Esempi di codice
PHP
<?php
function cofferweb($percorso, $metodo = 'GET', $dati = null) {
$ch = curl_init('https://app.cofferweb.com/api/v1/' . $percorso);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $metodo);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'X-API-Key: ' . getenv('COFFERWEB_API_KEY'),
'Content-Type: application/json'
));
if ($dati !== null) {
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($dati));
}
$risposta = curl_exec($ch);
$stato = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$corpo = json_decode($risposta, true);
if ($stato >= 400) {
throw new Exception($corpo['errore']['messaggio'] . ' (' . $corpo['errore']['codice'] . ')');
}
return $corpo;
}
// crea e invia
$creata = cofferweb('fatture', 'POST', array(
'cliente_id' => 42,
'righe' => array(
array('descrizione' => 'Abbonamento annuale', 'quantita' => 1, 'prezzo' => 250, 'iva' => 22)
)
));
$id = $creata['fattura']['id'];
$inviata = cofferweb('fatture/' . $id . '/sdi', 'POST');
echo 'Fattura ' . $inviata['fattura']['codice'] . ' - stato: ' . $inviata['fattura']['stato'];
JavaScript (Node)
const BASE = 'https://app.cofferweb.com/api/v1/';
async function cofferweb(percorso, metodo = 'GET', dati) {
const risposta = await fetch(BASE + percorso, {
method: metodo,
headers: {
'X-API-Key': process.env.COFFERWEB_API_KEY,
'Content-Type': 'application/json'
},
body: dati ? JSON.stringify(dati) : undefined
});
const corpo = await risposta.json();
if (!risposta.ok) {
throw new Error(`${corpo.errore.messaggio} (${corpo.errore.codice})`);
}
return corpo;
}
const { fattura } = await cofferweb('fatture', 'POST', {
cliente_id: 42,
righe: [{ descrizione: 'Abbonamento annuale', quantita: 1, prezzo: 250, iva: 22 }]
});
await cofferweb(`fatture/${fattura.id}/sdi`, 'POST');
12Buone pratiche
Separa creazione e invio
Crea sempre la fattura; invia al SDI solo quando sei sicuro (per esempio dopo l’incasso confermato). Fra i due passaggi il documento resta modificabile anche a mano dal gestionale: è l’ultima occasione per correggere.
Registra l’id accanto all’ordine
Non c’è deduplicazione automatica: due POST /fatture identici creano due
fatture con due numeri. Se la chiamata va in timeout ma la fattura era stata creata, ritrovala
con GET /fatture invece di crearne un’altra.
Non fare polling stretto
Per le notifiche SDI un controllo ogni 15 minuti è abbondante. Non c’è un limite fisso di chiamate, ma ogni chiamata inutile è tempo tolto alle pagine dei tuoi utenti: le API girano sullo stesso server del gestionale.
Tratta il 422 come un problema di dati
Non è un guasto e non va ritentato identico: manca un’informazione. Mostralo a chi può
correggerlo, con il messaggio che ti arriva.
13Domande frequenti
Posso creare il cliente dalle API?
In questa versione no: la fattura si emette a un cliente già presente in anagrafica. È una
scelta voluta — l’anagrafica è il punto in cui si concentrano codice destinatario, PEC,
listini e condizioni, e conviene che resti governata da chi gestisce l’azienda. Se ti serve,
dillo: è il primo candidato per la v2.
Le fatture create dalle API sono diverse dalle altre?
No: stessa numerazione, stessa lista, stessi PDF. Sono normali fatture, modificabili finché non
vengono inviate al SDI. Nello storico modifiche risultano firmate dall’utente a cui è
collegata la chiave.
Posso modificare o cancellare una fattura dalle API?
No. Modifica e cancellazione restano nel gestionale, dove ci sono i controlli del caso (e dopo
l’invio al SDI un documento non si tocca più comunque).
Come faccio le prove senza emettere documenti veri?
Crea la fattura ma non chiamare /sdi: resta una bozza a tutti gli
effetti, visibile e cancellabile dal gestionale. L’invio è l’unico passaggio non
reversibile.
La chiave è finita nel posto sbagliato (log, repository, e-mail).
Revocala subito da Impostazioni › Chiavi API e generane
un’altra. La revoca ha effetto immediato: la richiesta successiva riceve 403.
Le note di credito?
Sì: "nota_credito": true e, se la colleghi a una fattura, l’id di
quella in riferimento_fattura.
Le domande sul funzionamento del gestionale (fatture scartate, PEC, bozze, magazzino, utenti…) stanno nelle Domande frequenti.