Domande frequenti › Guida alle API

API cofferweb — versione 1

Come collegare un sistema esterno a cofferweb per emettere fatture, inviarle al Sistema di Interscambio e rileggerne l’esito, senza che nessuno debba entrare nel gestionale.

REST · JSON Autenticazione a chiave Versione v1 · stabile

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.
1
PrepariLeggi clienti e aliquote IVA disponibili GET /anagrafiche
2
CreiLa fattura nasce come bozza modificabile POST /fatture
3
InviiGenerazione XML e trasmissione al SDI POST /fatture/{id}/sdi
4
VerifichiLeggi la notifica quando arriva GET /fatture/{id}/notifica

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.

CosaDove si controllaPerché 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 APIGenera 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

cw_a43b5_9f1c4d7e2b8a05c36f1d9e4b7a2c8d05e3f6a1b4  │   └──┬──┴───────────────┴────────────────────────────├ prefisso azienda segreto (solo l’impronta resta sul server)

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 403 e 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:

Richiesta
curl https://app.cofferweb.com/api/v1/ping \
  -H "X-API-Key: cw_a43b5_LA_TUA_CHIAVE"
Risposta
{
  "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

ElementoValore
Indirizzo di basehttps://app.cofferweb.com/api/v1/
AutenticazioneX-API-Key: cw_…  oppure  Authorization: Bearer cw_…
FormatoJSON in ingresso e in uscita, sempre, anche per gli errori
CodificaUTF-8
Corposolo 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

GET /api/v1/ indice del servizio

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"]
}
GET /ping verifica la chiave

Conferma che la chiave è valida e attiva, e dice su quale azienda sta lavorando. Non modifica nulla: è la chiamata da usare nei controlli automatici.

GET /anagrafiche cosa posso usare

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.

POST /fatture crea la fattura
Richiesta
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

CampoObbl.DefaultNote
cliente_id id da /anagrafiche. Intestazione, indirizzi, partita IVA e listino vengono presi dall’anagrafica
righealmeno una
datanooggiformato gg/mm/aaaa
elettronicanotruefalse per una fattura non elettronica
pagamento_fenoMP05codice SDI del metodo di pagamento
ibannoquello in Preferenzeusato nell’XML
codice_destinatarionoquello del cliente7 caratteri, per B2B
pec_destinatarionoquella del clientealternativa al codice destinatario
codice_panose valorizzato la fattura diventa verso Pubblica Amministrazione
nota_creditonofalsetrue per una nota di credito
riferimento_fatturanoid della fattura a cui si riferisce la nota di credito
esigibilita_ivanoII immediata, D differita, S scissione dei pagamenti
tipo_documentono0tipo documento SDI (TD01, TD04…) quando serve forzarlo
payment_idnoquello del clientemetodo di pagamento interno del gestionale
banca_idno0banca d’appoggio del documento
note · note_piepagina · note_interne · causale novuotitesti liberi

Campi di riga

CampoObbl.DefaultNote
descrizionequello che il cliente legge in fattura
quantitano1
prezzono0unitario, IVA esclusa
ivano0percentuale; deve corrispondere a un’aliquota configurata
tax_idnoin alternativa a iva, l’id preciso dell’aliquota. Se c’è, vince su iva
scontono0percentuale sulla riga
prodotto_idno0collega 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.

Risposta · 201 Created
{ "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.

POST /fatture/{id}/sdi invia al Sistema di Interscambio
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.

GET /fatture/{id} a che punto siamo

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 }
    ]
} }
GET /fatture/{id}/notifica l’esito, tradotto
{ "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.

GET /fatture/{id}/xml scarica l’XML
{ "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.

GET /fatture elenco

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.

CampoTipoSignificato
idinteroidentificativo interno: è quello da conservare
numerointeronumero progressivo della fattura
codicetestocodice completo del documento (es. FT137)
datatestogg/mm/aaaa
clientetestointestazione del cliente
imponibile · iva · totaledecimaletotali del documento
statotestovedi la tabella qui sotto
inviata_al_sdibooleanose la trasmissione è avvenuta
data_invio_sditesto o nullgg/mm/aaaa hh:mm
notifica_sditesto o nullsigla dell’ultima notifica (RC, NS…)
notifica_descrizionetesto o nullla stessa notifica spiegata in italiano
file_xmltesto o nullnome del file XML generato
righeelencosolo in GET /fatture/{id}

8Stati e notifiche SDI

Il campo stato

StatoSignificatoCosa fare
da_inviarecreata, mai trasmessainviala quando vuoi; nel frattempo è modificabile
inviatatrasmessa, in attesa di rispostaaspetta la notifica
notificatail SDI ha rispostoleggi notifica_sdi
scartatascarto: la fattura non risulta emessacorreggi e riemetti

Le sigle delle notifiche

CodiceCosa vuol direServe fare qualcosa?
RCconsegnata al destinatariono
DTdecorrenza termini: il destinatario non si è espressono, la fattura è valida
MCmancata consegna: valida, messa a disposizione nell’area riservata del clienteavvisa il cliente, non riemettere
ATattestazione di trasmissione (destinatario estero o canali non disponibili)trasmetti l’attestazione al cliente
NSscarto: la fattura non risulta emessacorreggi e ritrasmetti entro 5 giorni
NE / ECil destinatario ha accettato o rifiutatose rifiutata, verifica il motivo
SEscarto dell’esito inviato dal committentedi norma nulla per chi emette
MTfile dei metadatino, è 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.

StatoCodiceCosa è successoCosa fare
400json_non_validoil corpo non è JSON validocontrolla virgole e virgolette
401chiave_mancantenon hai mandato l’intestazioneaggiungi X-API-Key; se ci sei sicuro, vedi la nota sull’.htaccess
401chiave_non_validachiave sbagliata, storpiata o azienda inesistentericontrolla, eventualmente rigenerala
403chiave_revocatala chiave è stata revocata dal gestionaleusane una attiva
404non_trovatal’id non esiste per questa aziendaverifica l’id
404xml_non_disponibileXML non ancora generato, o file assenteinvia prima al SDI
404percorso_non_trovatoindirizzo scritto maleconfronta con l’elenco dei comandi
405metodo_non_consentitometodo HTTP sbagliato su quel percorsoes. POST dove serve GET
409gia_inviatafattura già trasmessa al SDInon reinviare: leggi lo stato
422cliente_mancante / cliente_non_trovatocliente_id assente o inesistenteprendi l’id da /anagrafiche
422righe_mancanti / riga_non_validanessuna riga, o riga senza descrizionecompleta i dati
422aliquota_non_trovatal’IVA richiesta non è configuratausa una delle aliquote nei dettagli
422documento_incompletomancano i dati per la fattura elettronicaaggiungi codice destinatario/PEC, pagamento, IBAN
500errore_internola fattura non è stata creatariprova; se persiste, apri un ticket con il messaggio
502invio_fallitoproblema nel dialogo con il SDIriprova 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.